CCPC - E. Exchanging Gifts (思维+拓扑序),I(思维,组合数)

26 篇文章 5 订阅
E. Exchanging Gifts

题意
一共 m 个人,初始每个人的手中都有一种礼物 a i a_i ai
两两之间可以互换礼物,如果最终每个人的手中和初始礼物不同,那么这个人是高兴的。
问,最多能使多少人高兴?

m 可能很大,所以将会给出 n     ( 1 ≤ n ≤ 1 0 6 ) n \,\,\, (1≤n≤10^6) n(1n106) 个序列 s i s_i si,初始礼物序列 a [ ] a[] a[] 为序列 s n s_n sn,第 i 个序列 s i s_i si 采取如下的输入方法:

  • 1     k     q [ 1.. k ]     ( 1 ≤ k ≤ 1 0 6 , 1 ≤ q i ≤ 1 0 9 ) 1\,\,\,k\,\,\, q[1..k]\,\,\, (1≤k≤10^6, 1≤q_i≤10^9) 1kq[1..k](1k106,1qi109):表示 s i = [ q 1 , q 2 , … , q k ] s_i=[q_1,q_2,…,q_k] si=[q1,q2,,qk]
  • 2     x     y     ( 1 ≤ x , y ≤ i − 1 ) 2\,\,\,x\,\,\,y\,\,\,(1≤x,y≤i−1) 2xy(1x,yi1):表示 s i = s x + s y s_i = s_x + s_y si=sx+sy.

T ( 1 ≤ T ≤ 10000 ) T (1≤T≤10000) T(1T10000) 组数据, n n n k k k 的总和都不超过 1 e 6 1e6 1e6

思路
首先是个小思维,如果已知初始序列,高兴人数最多最多为多少?
找到序列中 出现次数最多的数的次数 x其余数的个数 y

  • x>y 时,高兴值最大为 2*y;
  • x<y时,高兴值最大为 x+y。

所以,需要统计出序列 s n s_n sn 中出现次数最多的数的出现次数。
s n s_n sn 可能由其他两个序列组成,而那两个序列由另外两个序列组成。。
可以找出最终的序列一共由多少个给定的序列组合而成,每个给定的序列分别用了多少次。

若干个有向边不会构成环,所以为拓扑图。

要求出每个叶子节点一共用了多少次,也就是求从 n 节点到 每个叶子节点 分别有多少条路径。
用拓扑序维护:从上往下走,每次到更新的节点 tx 的路径数 dist[tx] 加上 到当前节点 x 的路径数dist[x]。然后入度-1,把入度为0的点加入队列,该节点不会被更新了,用该节点去更新其他点。

需要注意的是,如果所有节点的入度都统计了,而又一个点 x 也没有入度(相当于根节点 n),那么只把 n 节点放到拓扑序列,那么a节点和b节点的入度由于节点x的限制永远不会变为0,就不会入队更新下面的节点,就出现错误了。

解决方法有两个:

  1. 我们要求的是从 n 节点到每个叶子节点的路径数,所以统计入度的时候只要从 n 节点出发到叶子节点可能经过的那一部分点统计入度(从 n 节点 bfs 一遍)。然后初始只把 n 节点放到拓扑序列中,dist[n]设为1。
  2. 初始把所有入度为 0 的节点都放入拓扑序列,只把dist[n]设为1,其余设为0,这样 x 也会出队,就把对 a节点 b节点的入度限制消除了。
    在这里插入图片描述

输入量较大,需要快读。

static char buf[100000],*pa=buf,*pd=buf;
#define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
inline int read()
{
    register int x(0);register char c(gc);
    while(c<'0'||c>'9')c=gc;
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
    return x;
}

实现1:

#include<bits/stdc++.h>
using namespace std;

#define mem(a,b) memset(a,b,sizeof a)
#define ll long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

/**/

const int N = 2000010, mod = 1e9+7;
int T, n, m;
vector<int> v[N], e[N];
ll f[N], dist[N];
map<int, ll> mp;
bool vis[N];
int ru[N];

static char buf[100000],*pa=buf,*pd=buf;
#define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
inline int read()
{
    register int x(0);register char c(gc);
    while(c<'0'||c>'9')c=gc;
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
    return x;
}

void init()
{
	mp.clear();
	for(int i=1;i<=n;i++)
	{
		f[i] = dist[i] = 0;
		vis[i] = ru[i] = 0;
		e[i].clear();
		v[i].clear();
	}
}

void bfs()
{
	queue<int> que;
	que.push(n);
	vis[n] = 1;
	
	while(que.size())
	{
		int x = que.front();
		que.pop();
		
		for(auto tx : e[x])
		{
			ru[tx]++;
			if(vis[tx]) continue;
			vis[tx] = 1;
			que.push(tx);
		}
	}
}

void topsort()
{
	queue<int> que;
	que.push(n);
	dist[n] = 1;
	
	while(que.size())
	{
		int x = que.front();
		que.pop();
		
		for(auto tx : e[x])
		{
			dist[tx] += dist[x];
			
			ru[tx] --;
			if(ru[tx] == 0) que.push(tx);
		}
	}
}

signed main(){
	T = read(); 
	while(T--)
	{
		n = read();
		init();
		for(int i=1;i<=n;i++)
		{
			int op;
			op = read();
			if(op == 1)
			{
				f[i] = 1;
				int k; k = read();
				for(int j=1;j<=k;j++){
					int x; x = read();
					v[i].pb(x);
				}
			}
			else
			{
				int x, y;
				x = read();
				y = read();
				e[i].pb(x);
				e[i].pb(y);
			}
		}
		
		bfs();
		topsort();
		
		ll maxa = 0, sum = 0;
		for(int i=1;i<=n;i++)
		{
			if(!f[i]) continue;
			for(auto x : v[i])
			{
				mp[x] += dist[i];
				maxa = max(maxa, mp[x]);
				sum += dist[i];
			}
		}
		
		if(maxa > sum-maxa) printf("%lld\n", 2*(sum-maxa));
		else printf("%lld\n", sum);
	}
	
	return 0;
}
/*
1
6
1 3 3 3 2
1 4 2 2 3 3
2 1 2
2 3 5
2 3 2
2 2 5
*/

实现2:

#include<bits/stdc++.h>
using namespace std;

#define mem(a,b) memset(a,b,sizeof a)
#define ll long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

/**/

const int N = 2000010, mod = 1e9+7;
int T, n, m;
vector<int> v[N], e[N];
ll f[N], dist[N];
map<int, ll> mp;
bool vis[N];
int ru[N];

static char buf[100000],*pa=buf,*pd=buf;
#define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
inline int read()
{
    register int x(0);register char c(gc);
    while(c<'0'||c>'9')c=gc;
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
    return x;
}

void init()
{
	mp.clear();
	for(int i=1;i<=n;i++)
	{
		f[i] = dist[i] = 0;
		vis[i] = ru[i] = 0;
		e[i].clear();
		v[i].clear();
	}
}

void topsort()
{
	queue<int> que;

	for(int i=1;i<=n;i++)
		if(ru[i] == 0){
			que.push(i);
			if(i==n) dist[i] = 1;
		}
	
	while(que.size())
	{
		int x = que.front();
		que.pop();
		
		for(auto tx : e[x])
		{
			dist[tx] += dist[x];
			
			ru[tx] --;
			if(ru[tx] == 0) que.push(tx);
		}
	}
}

signed main(){
	T = read(); 
	while(T--)
	{
		n = read();
		init();
		for(int i=1;i<=n;i++)
		{
			int op;
			op = read();
			if(op == 1)
			{
				f[i] = 1;
				int k; k = read();
				for(int j=1;j<=k;j++){
					int x; x = read();
					v[i].pb(x);
				}
			}
			else
			{
				int x, y;
				x = read();
				y = read();
				e[i].pb(x);
				e[i].pb(y);
				ru[x] ++ , ru[y] ++; 
			}
		}
		
		topsort();
		
		ll maxa = 0, sum = 0;
		for(int i=1;i<=n;i++)
		{
			if(!f[i]) continue;
			for(auto x : v[i])
			{
				mp[x] += dist[i];
				maxa = max(maxa, mp[x]);
				sum += dist[i];
			}
		}
		
		if(maxa > sum-maxa) printf("%lld\n", 2*(sum-maxa));
		else printf("%lld\n", sum);
	}
	
	return 0;
}

经验
场上调了两个多小时没调出来,一开始以为是二叉树直接用搜索干的,后面发现不对,是拓扑图。
后面没注意到上面这个问题,又调了好长时间,随便写了个样例发现的。
最后搞好了发现超时了,没用快读。。

以后应该算一下输入规模判断下是不是要用快读。
还有多写几个样例。


I. Interesting Permutation

题意
对于一个全排列数组 a [ ] a[] a[],定义:

  • 对于 1 ≤ i ≤ n , f i = max ⁡ { a 1 , a 2 , … , a i } 1 \leq i \leq n, f_{i}=\max \left\{a_{1}, a_{2}, \ldots, a_{i}\right\} 1in,fi=max{a1,a2,,ai};
  • 对于 1 ≤ i ≤ n , g i = min ⁡ { a 1 , a 2 , … , a i } 1 \leq i \leq n, g_{i}=\min \left\{a_{1}, a_{2}, \ldots, a_{i}\right\} 1in,gi=min{a1,a2,,ai};
  • 对于 1 ≤ i ≤ n , h i = f i − g i 1 \leq i \leq n, h_{i}=f_{i}-g_{i} 1in,hi=figi.

现在给出 h [ ] h[] h[] 数组,问一共有多少种 a [ ] a[] a[] 构造方案?

思路
首先列出限制条件

  • 第一个位置一定为0;
  • 根据定义,数列一定单调不递减;
  • 因为是 n 的全排列,最后一位置肯定为 n-1.

考虑有多少种情况,用分支组合计数的思想:

  • 对于第一个位置,只有一种情况;
  • 对于后面的位置:
    • 如果其值大于上一位置的话,那么其取值要么比最大值大 h [ i ] − h [ i − 1 ] h[i]-h[i-1] h[i]h[i1],要么比最小值小 h [ i ] − h [ i − 1 ] h[i]-h[i-1] h[i]h[i1],有 2 种情况;
    • 如何其值和上一位置相等,那么这个位置可以取最小值和最大值中间未出现的任意一个,有 c n t cnt cnt 种情况。

所以要标记最小值和最大值之间有多少个未出现的数,对于第一种类型,未出现个数增加 h [ i ] − h [ i − 1 ] − 1 h[i]-h[i-1]-1 h[i]h[i1]1;对于第二种类型,选择一个数后未出现个数 c n t − 1 cnt-1 cnt1

将每个位置的情况数相乘,就是最终的情况数。

Code

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define endl '\n'

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

signed main(){
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d", &n);
		for(int i=1;i<=n;i++) scanf("%d", &a[i]);
		
		int flag = 0;
		if(a[1] != 0 || a[n] != n-1) flag = 1;
		
		ll ans = 1, left = 0;
		for(int i=2;i<=n;i++)
		{
			if(a[i] > a[i-1])
			{
				ans = ans*2%mod;
				left += a[i]-a[i-1]-1;
			}
			else if(a[i] == a[i-1])
			{
				ans = ans*left%mod;
				left --;
			}
			else flag = 1;
		}
		
		if(flag) printf("0\n");
		else printf("%lld\n", ans);
	}
	
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值