2019CCPC哈尔滨站 E题 gym102394 Exchanging Gifts 拓扑排序做法

题意:两种操作,

1.给一个序列

2.给两个数字x,y,代表本次操作是将x,y两个串拼接成一个大串。(x,y均为之前某次操作得到的串)

操作总数1e6,给出的序列长度1e6,由2操作得到的串的长度保证不超过1e18,求最后一个操作得到的串的快乐值是多少。

快乐值的定义:任意排列这个串,使得这个串尽量多的位置的数字与原来的数字不同,不同的位置的总数即为快乐值。

如果知道最终串的每一个数字出现次数,只需要拿出现最多的数字的出现次数maxx和串的总长度sum比较就能算出答案——即当maxx*2>sum时,快乐值为(sum-maxx) * 2,否则为sum。

也就是这个题的任务就是算出最后一个串的每个数字的出现次数和总长度。但是我们会发现每次操作得到的串又有很多数字,在合并时会非常难计算。所以我的想法是先去记录每个操作得到的序列被用了多少次,再把这些序列的每个数字 * 次数就得到了最终串的每个数字出现次数。

然后我们考虑倒推的过程,因为我们是知道最后一个序列是由哪两个序列组成的,那么我们就可以知道这两个序列被用了一次,再从这两个序列往前递推。假设某个序列被用了Q次,那么组成它的两个序列被用的次数都要 += Q。于是我们很容易想到拓扑排序,假设x,y组成了a,则从a分别向x,y连一条有向边。但是并不能直接拓扑,需要算出哪些序列是有用的,否则入度的计算会出现问题。比如5,8这两个序列组成了序列9,会从9向5,8连边。5,8又组成了序列10,会从10向5,8连边,10是最终序列。那么按照倒着拓扑的思想,会从10出发,然后将5,8的入度--,但是我们会发现,此时5,8的入度并不为0,所以不会被加入到队列中。所以说我们要先bfs一遍,找到所有由终点推得的点的一个局部图,此时每个点的入度才是我们想要的。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb emplace_back
#define mp make_pair
#define se second
#define endl '\n'
#define IO ios::sync_with_stdio(false);cin.tie(0)
const int maxn = 1e6 + 5;
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
ll cnt[maxn];//每个序列的使用次数 
int ru[maxn];
bool vis[maxn];
vector<int>ff[maxn], co[maxn];
unordered_map<int, ll>ans;
queue<int>q;
int t, n, m, flag[maxn];
#define tpyeinput int
inline char nc() {static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
inline void read(tpyeinput &sum) {char ch=nc();sum=0;while(!(ch>='0'&&ch<='9')) ch=nc();while(ch>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch-48),ch=nc();}
void init(){
	while(!q.empty()) q.pop();
	for(int i = 1; i <= n; i++){
		co[i].clear();
		ff[i].clear();
		cnt[i] = ru[i] = vis[i] = 0;
	}
	ans.clear();
}
 
void topu(){
	q.push(n);
	while(!q.empty()){
		ll now = q.front();
		q.pop();
		for(auto nxt : ff[now]){
			ru[nxt]--;
			cnt[nxt] += cnt[now];
			if(ru[nxt] == 0)
				q.push(nxt);
		}
	}
}
 
void bfs(){
	q.push(n);
	vis[n] = 1;
	while(!q.empty()){
		ll now = q.front();
		q.pop();
		for(auto nxt : ff[now]){
			ru[nxt]++;
			if(!vis[nxt])
				q.push(nxt), vis[nxt] = 1;
		}
	}
}
 
void solve(){
	for(int i = 1; i <= n; i++){
		if(cnt[i] != 0 && flag[i] == 1){//当它是给定的初始序列时才去计算它 
			for(auto j : co[i])
				ans[j] += cnt[i];
		}
	}
	ll sum = 0, maxx = 0;
	for(auto &i : ans){
		sum += i.se;
		if(i.se > maxx)
			maxx = i.se;
	}
	if(maxx * 2 >= sum)
		cout << (sum - maxx) * 2 << endl;
	else
		cout << sum << endl;
}
int main(){
	IO;
	read(t);
	while(t--){
		read(n);
		init();
		int a, b;
		for(int i = 1; i <= n; i++){
			read(flag[i]);
			if(flag[i] == 1){
				read(m);
				for(int j = 1; j <= m; j++){
					read(a);
					co[i].pb(a);
				}
			}else{
				read(a);read(b);
				ff[i].pb(a);
				ff[i].pb(b);
			}
		}
		cnt[n] = 1;
		bfs();//找出有用的序列
		topu();
		solve(); 
	}
}

在CF上跑了一下,1s的时限跑了998ms呜呜呜,不过代码确实还有很大的优化空间(比如map,读入输出之类的)

update:鉴于被时限卡的太死,博客不应该放一个差点T的代码。于是我加了读入挂(原来998ms的代码是关同步后的cin cout),现在不到500ms了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值