题意:
就是给你n次操作,一种是给你直接给你一个序列,一种是这个序列为前面的某两个a和b序列拼接起来。现在问你第n个序列,这个序列是一些值,这个序列再随意排列后再相应匹配,问你最多有多少个人满足他目前的数和处理后的数不一样。
思考:
假如你有两个序列,排序后最多有多少人的值不同。这个就是一个数学结论,比如出现次数最大的次数为n,剩下的总和为m,如果n>m那么答案就是2*m,否则就是n+m。当n>m的时候,左边和右边相互匹配。当n<m的时候,那么就可以迭代的替代,第一大的代替第二大的,依次类推,最终就可以得到n+m人都不一样。
那么现在就是处理怎么得到这些数了。注意到要么是一个序列,要么是前面的某两个序列拼接起来,但是拼接拼接最后这个数组很大啊,不可能直接取枚举一遍。所以就转化成对于第n个序列,到底是由多少个基础序列得到的就这样就可以了。刚开始用了个dfs直接从n开始搜,但是复杂度高超时了。然后就画画图发现dfs不止一遍,所以现在就是从n点到叶子节点由多少条路径的问题,这不就是拓扑记录方案数吗。然后直接拓扑序一遍,但是又发现有些点的入读不可能变成0,这个就要注意了,刚开始入队的时候,让所有in为0的进去就行了,这样才能保证每个点都会进入。只让n点初始为1就可以了。然后就完了。当然这个cf上面的评测有点慢,据说现场评测比较快。
代码:
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define ll long long
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int N = 1e6+5;
int T,n;
int vis[N];
ll dist[N],in[N];
unordered_map<int,ll > mp;
vector<int > v[N],e[N];
void init()
{
mp.clear();
for(int i=0;i<=n;i++)
{
vis[i] = dist[i] = in[i] = 0;
v[i].clear();e[i].clear();
}
}
void topsort()
{
queue<int > q;
dist[n] = 1;
for(int i=1;i<=n;i++)
{
if(!in[i]) q.push(i);
}
while(q.size())
{
auto now = q.front();
q.pop();
for(auto spot:v[now])
{
dist[spot] += dist[now];
if(--in[spot]==0) q.push(spot);
}
}
}
signed main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
init();
for(int i=1;i<=n;i++)
{
int op,m,x;
scanf("%d",&op);
if(op==1)
{
scanf("%d",&m);
vis[i] = 1;
while(m--)
{
scanf("%d",&x);
e[i].pb(x);
}
}
else
{
int a,b;
scanf("%d%d",&a,&b);
v[i].pb(a);v[i].pb(b);
in[a]++,in[b]++;
}
}
topsort();
for(int i=1;i<=n;i++)
{
if(!vis[i]) continue;
for(auto t:e[i]) mp[t] += dist[i];
}
ll maxn = 0,sum = 0;
for(auto t:mp)
{
maxn = max(maxn,t.se);
sum += t.se;
}
if(maxn>sum-maxn) printf("%lld\n",2*(sum-maxn));
else printf("%lld\n",sum);
}
return 0;
}
总结:
多多思考多多培养思维把。