CCPC2019哈尔滨E题——拓扑排序|数学

题目链接:https://codeforces.com/gym/102394/problem/E

题目大意:

总共有两种操作:

1.给一个长度为m的序列

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

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

题解:

比赛时三个人一起开了这个银牌题,不难想出一个序列的最大快乐值,all表示所有数出现的总次数,sum表示一个数出现的最大次数。当all/2<sum时,答案为2*(all-sum),否则为all。之后我们开始混乱了,最后写了一个暴力的,发现t了,又发现可以记录一下每个序列的次数,然后又t了,最后队友转换成树来做,还是T,真是自闭。

这个题有两种解法O(nlogn)和O(n)的解法,而且会卡掉常数比较大的O(nlogn)的写法,O(n)的解法肯定可以过。

先讲时间复杂度比较大的解法,不难发现这种合并操作类似于拓扑排序,因此我们就可以根据组成序列的顺序建图,即如果x和y组成了序列z,那么z向x和y建一条边,然后按照拓扑序列进行bfs,最后记录一下答案就行了。

具体的时间复杂度我也不会分析,大概是O(e+n),肯定会比O(n)大,比较容易被卡常数,注意找个好的快读板子。

trick:注意不能建好图之后就直接拓扑排序,因为那些对答案没有贡献的序列是不需要处理的,所以我们要先bfs搜出一个局部的和答案相关联的序列的图。

最后是O(n)的解法,我们可以直接从n到1枚举那些组合的序列,维护最后对答案贡献的序列的出现次数,然后利用一个题里面的O(n)求众数的解法(BZOJ2456: mode(众数))求出出现次数最大的数,维护答案即可。

解法一代码实现:

#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 N = 1e6 + 5;
ll cnt[N];//每个序列的使用次数 
int ru[N];
bool vis[N];
vector<int>G[N], co[N];
unordered_map<int, ll>ans;
queue<int>q;
int t, n, m, flag[N];
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(int &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();
		G[i].clear();
		cnt[i] = ru[i] = vis[i] = 0;
	}
	ans.clear();
}
 
void tuopu(){
	q.push(n);
	while(!q.empty()){
		ll now = q.front();
		q.pop();
		for(auto nxt : G[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 : G[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);
				G[i].pb(a);
				G[i].pb(b);
			}
		}
		cnt[n] = 1;
		bfs();//找出有用的序列
		tuopu();
		solve(); 
	}
}

解法二代码实现:

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define debug printf("ac\n");
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int INF = 0x3f3f3f3f;
const int N = 1e6+7;
vector<int> G[N];
ll num[N],all,MAX,sum;
int op[N][3];
int T,n,m;
int main(){
    T=read();
    while(T--){
        n=read();
        rp(i,1,n){
            op[i][0]=read();
            num[i]=0,G[i].clear();
            if(op[i][0]==1){
                m=read();
                rp(j,1,m){
                    int x=read();
                    G[i].pb(x);
                }
            }
            else{
                op[i][1]=read();
                op[i][2]=read();
            }
        }
        num[n]=1;
        RP(i,n,1){
            if(op[i][0]==2){
                num[op[i][1]]+=num[i];
                num[op[i][2]]+=num[i];
            }
        }
        sum=MAX=all=0;
        rp(i,1,n){
            if(num[i]!=0&&op[i][0]==1){
                for(auto val:G[i]){
                    if(MAX==0) MAX=val,sum+=num[i];
                    else if(val==MAX) sum+=num[i];
                    else{
                        sum-=num[i];
                        if(sum<0){
                            sum=-sum;
                            MAX=val;
                        }
                    } 
                    all+=num[i];
                }
            }
        }
        sum=0;
        rp(i,1,n){
            if(op[i][0]==1&&num[i]!=0){
                for(auto val:G[i]){
                    if(val==MAX)
                        sum+=num[i];
                }
            }
        }
        if(sum<=all/2) printf("%lld\n",all);
        else printf("%lld\n",(all-sum)*2);
    }
    return 0;
}
/*
2
1
1 5 3 3 2 1 3
3
1 3 3 3 2
1 4 2 2 3 3
2 1 2
*/

参考链接1:gym102394 Exchanging Gifts 拓扑排序做法 

参考链接2:Exchanging Gifts Gym - 102394E(数学)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值