10.18 考试
作者:互联网
填坑ing
T1 bzoj 4057
Desprition
有 \(n\) 个人之间互相欠钱,\(A\) 欠 \(B\) 钱记为 \(d[a][b]\), 保证 \(d[a][b]=-d[b][a]\) 。如果一个人入不 敷出(收入小于欠债)则会破产并消除所有与他有关的债务。破产不是一瞬间完成的,只有第 一个人破产后,接下来可能破产的人才会再继续破产,直到稳定。不同的结局将取决于谁先 破产。对于每个人,是否存在一种结局使得该人是唯一的幸者。
input
第一行一个正整数 \(T\) ,表示有 \(T\) 组数据。 每组数据第一行一个正整数 \(n\) ,接下来 \(n\) 行,每行 \(n\) 个整数,第 \(i\) 行第 \(j\) 个整数表示 \(d[i][j]\), 保证有\(d[i][i] = 0, d[i][j] = -d[j][i],\) \(|d[i][j]| <=10^6\)。
output
每组数据输出一行,按照升序输出所有可能的人编号,空格隔开,如果没有一个人能满足条 件,输出一个 0
对于 100% 的数据 \(n\leq 20\)
sloution
\(n\) 的范围很小,考虑状压 \(dp\)
设 \(f[i]\) 表示当前 \(i\) 这个集合里面的人全部破产能否达成 \(0表示没破产,1表示破产\)。
转移的时候枚举每个没有破产的人 \(j\),若这个人在 \(i\) 这个状态下可以破产,则 $f[i $ \(or\) \((1<<(j-1))]\) \(|= f[i]\)
复杂度为 \(O(2^n n)\) 。理论上来说是跑不过去的。实际上加个优化就过了。(我就这样被 \(T\) 成了 30分)
一个优化,如果 \(f[i] == 0\) 我们可以直接跳过,不用再枚举他。
最后看 \(f[(1<<n)-1 - (1<<(i-1))]\) 是否为 \(1\) ,若为 \(1\) 则 \(i\) 这个人可能成为最后的赢家。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = (1<<20);
int T,n,flag,a[21][21],sum[21],f[N+10];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
int main()
{
T = read();
while(T--)
{
n = read();
memset(f,0,sizeof(f));
memset(sum,0,sizeof(sum));
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
a[i][j] = -1 * read();
sum[i] += a[i][j];
}
}
for(int i = 1; i <= n; i++)
{
if(sum[i] < 0) f[1<<(i-1)] = 1;
}
for(int i = 1; i < (1<<n)-1; i++)
{
if(f[i])
{
for(int j = 1; j <= n; j++)
{
if(((i>>(j-1)) & 1) == 0)
{
int num = 0;
for(int k = 1; k <= n; k++)
{
if(((i>>(k-1))&1) == 1)
{
num += a[j][k];
}
}
if(sum[j] - num < 0) f[i|(1<<(j-1))] |= f[i];
}
}
}
}
int M = (1<<n)-1, flag = 0;
for(int i = 1; i <= n; i++)
{
if(f[M-(1<<(i-1))] == 1)
{
flag = 1;
printf("%d ",i);
}
}
if(flag == 0) printf("%d",0);
printf("\n");
}
return 0;
}
T2 bzoj 5450
Desprition
\(n\) 个点,\(m\) 条边的有向图,每次选择若干点将其标记,不能存在两个不同的点 \(i,j\) 满足 可以从 \(i\) 到达 \(j\) 。问最少取几次可以使所有点都被标记过。
对于 100% 的数据 \(n\leq 10^6, m\leq 10^6\)
Sloution
显然在一个环中,我们要取完这个环至少需要 环上点的数量的步数。
这个可以直接拿 \(tarjain\) 解决。然后就转化成我们熟悉的 \(DAG\) 上的问题。
显然答案为 最长路的长度。直接拓扑排序就行。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N = 1e6+10;
int n,m,cnt,tot,num,top,ans,u,v;
int head[N],du[N],dfn[N],low[N],sta[N],f[N],shu[N],siz[N];
bool vis[N];
vector<int> e1[N];
struct node
{
int to,net;
}e[N];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void add(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void tarjain(int x)
{
dfn[x] = low[x] = ++num;
sta[++top] = x; vis[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(!dfn[to])
{
tarjain(to);
low[x] = min(low[x],low[to]);
}
else if(vis[to])
{
low[x] = min(low[x],dfn[to]);
}
}
if(dfn[x] == low[x])
{
cnt++; int y;
do
{
y = sta[top--];
shu[y] = cnt;
siz[cnt]++;
vis[y] = 0;
}while(x != y);
}
}
void rebuild()
{
for(int i = 1; i <= n; i++)
{
for(int j = head[i]; j; j = e[j].net)
{
int to = e[j].to;
if(shu[i] != shu[to])
{
du[shu[to]]++;
e1[shu[i]].push_back(shu[to]);
}
}
}
}
void topu()
{
queue<int> q;
for(int i = 1; i <= cnt; i++)
{
if(du[i] == 0)
{
q.push(i);
f[i] = siz[i];
}
}
while(!q.empty())
{
int t = q.front(); q.pop();
for(int i = 0; i < e1[t].size(); i++)
{
int to = e1[t][i];
f[to] = max(f[to],f[t] + siz[to]);
if(--du[to] == 0) q.push(to);
}
}
for(int i = 1; i <= cnt; i++) ans = max(ans,f[i]);
}
int main()
{
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
u = read(); v = read();
add(u,v);
}
for(int i = 1; i <= n; i++)
{
if(!dfn[i]) tarjain(i);
}
rebuild();
topu();
printf("%d\n",ans);
return 0;
}
T3 bzoj 4552
标签:ch,leq,int,10.18,num,破产,include,考试 来源: https://www.cnblogs.com/genshy/p/13882420.html