AtCoder Grand Contest 012 题解

50 篇文章 0 订阅
19 篇文章 0 订阅

A:

有3N个选手参加一场比赛,第i个人的力量值为ai,他们将每三个人分为一组,每组的力量值为三个人力量值的中位数。问这样分组后得到的N组里面,参赛队伍力量值的和的最大值是多少? N <= 10^5


solution:

考虑贪心解法,先将所有选手按照力量值升序排好,容易观察到最优的分法是让第n - 1,n - 3,n - 5...这些选手的力量值产生贡献。证明也很简单,如果只有三个人,显然成立。当人数大于3,第一个选手力量值最小,无法产生贡献,最后一个选手力量值太大,无法产生贡献,让他们分到一组,再加上第n - 1个人,这样肯定是很优的,于是就变成了3N - 3个人的子问题,证明完毕

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 3E5 + 30;
typedef long long LL;

int n,A[maxn];
LL Ans;

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n;
	for (int i = 1; i <= 3 * n; i++) scanf("%d",&A[i]);
	sort(A + 1,A + 3 * n + 1);
	for (int i = 0; i < n; i++) Ans += 1LL * A[3 * n - 1 - 2 * i];
	cout << Ans << endl;
	return 0;
}


B:

现在有一个n个点m条边的无向图,需要执行q次操作,每次操作是将距离点x不超过d的所有点都染成某种颜色,需要输出所有操作结束后每个点的颜色。n <= 10^5,0 <= d <= 10


solution:

注意到d很小,考虑设计一个和d相关的算法。将每个点拆成11个,每个拆出来的点储存从这个点出发,还需要传播k步的那种颜色。先将修改离线,因为后面的颜色会覆盖先前的颜色,所以当两种需要传播的颜色相遇时只保留较迟出现的那个就行了。按照修改的距离从大到小处理,每次把整张图遍历一遍,并把需要传播的颜色标记放到下一层即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 1E5 + 10;

struct Mark{
	int col,ti; Mark(){}
	Mark(int col,int ti): col(col),ti(ti){}
	bool operator > (const Mark &B) const {return ti > B.ti;}
}last[11][maxn],Ans[maxn];

int n,m,q;

vector <int> v[maxn];

inline Mark max(const Mark &x,const Mark &y) {return x > y ? x : y;}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n >> m;
	while (m--)
	{
		int x,y; scanf("%d%d",&x,&y);
		v[x].push_back(y); v[y].push_back(x);
	}
	cin >> q;
	for (int i = 1; i <= q; i++)
	{
		int x,d,c;
		scanf("%d%d%d",&x,&d,&c);
		Mark now = Mark(c,i);
		Ans[x] = max(Ans[x],now);
		last[d][x] = max(last[d][x],now);
	}
	for (int j = 10; j; j--)
		for (int i = 1; i <= n; i++)
		{
			if (!last[j][i].ti) continue;
			for (int k = 0; k < v[i].size(); k++)
			{
				int to = v[i][k];
				Ans[to] = max(Ans[to],last[j][i]);
				last[j - 1][to] = max(last[j - 1][to],last[j][i]);
			}
		}
	for (int i = 1; i <= n; i++) printf("%d\n",Ans[i].col);
	return 0;
}

-----------分割线-----------------

由于是第一次打AGC以及苟蒻实在是naive....后面的题都是赛后抄题解写的啦。。。


C:

我们定义一个字符串s是"good",当它能被拆分成xx的形式,也就是它是由一个字符串x复制一遍直接接在后面形成的。现在需要构建一个字符串s,使得它的所有2^|s|个子序列中,有且仅有N个子序列构成的字符串是"good"。|s|中的字符使用1~100的数字代替,1 <= |s| <= 200,1 <= N <= 10^12,可以证明总是有解的


solution:

考虑一个{1~n}的排列后面紧接着数字1~n,这样的一个串中"good"子序列的数量。因为"good"串是copy以后产生的,所以每种字符前半段出现后半段也要出现,而上面构造的那种串每种字符最多出现两次,所以当一个"good"被拆分成xx后,肯定前半段一个x后半段一个x,因此"good"子序列的数量就等于前半段上升子序列的数量了。

定义空串也属于"good"子序列,那么构造一个拥有N + 1个"good"子序列的东西就行了

对于一个{1~k}的排列{p1,p2,p3,...,pk},记当前拥有的"good"子序列数量为x

那么排列{p1,p2,p3,...,pk,k + 1}拥有的"good"子序列显然是x * 2

排列{k + 1,p1,p2,p3,...,pk}拥有的"good"子序列显然是x + 1

综上所述,一个简单的递归程序就能解决这个问题了,排列的大小n不超过2logn = 80,因此总长不超过160

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

typedef long long LL;

int cnt;
LL n;

deque <int> Q;

void Build(LL N)
{
	if (N == 1) return;
	if (N & 1)
	{
		Build(N - 1);
		Q.push_front(++cnt);
	}
	else
	{
		Build(N >> 1LL);
		Q.push_back(++cnt);
	}
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n; Build(n + 1); cout << (cnt << 1) << endl;
	while (!Q.empty()) printf("%d ",Q.front()),Q.pop_front();
	for (int i = 1; i < cnt; i++) printf("%d ",i); cout << cnt << endl;
	return 0;
}


D:

现在有N个球排成一行,第i个球的颜色为ci,重量为wi,给定参数X,Y,可以执行以下两种操作:1.选择两个颜色相同的球,如果它们的重量和不超过X,交换他们的位置。2.选择两个颜色不同的球,如果它们的重量和不超过Y,交换它们的位置。问一共可以构造出多少种不同的颜色序列?两种序列不同当且仅当某一位置上的颜色不同。1 <= n <= 2*10^5


solution:

球与球之间交换的可行性显然是满足传递性的,也就是说,有三个球a,b,c,如果a和b能交换,b和c能交换,通过(a,b)(b,c)(a,b)三次操作,就能实现b不动而交换a,c。那么就有一种简单的思路,暴力枚举每对球,如果能交换就在它们之间连一条边,这样就构成了一些连通块。因为连通块之间独立,所以对每个连通块,用组合数统计一下答案,最后乘起来就行了。

这样建图边数是O(N^2)的,显然过不去。

对于同一种颜色,拿出最轻的那个球记为a,如果这种颜色的两个球b,c能交换,那么它们肯定都能和a交换,于是对于同一种颜色的球,只需要考虑它们和最轻的那个球的关系就行了

对于颜色不同的情况,每种球取出最轻的那个,将所有取出的球排序,记最轻的两个分别为a1,a2,类似的,只要考虑a1,a2和其它颜色的球连的边就行了。对于两个不同颜色的球b,c,假设有边相连

如果b != a1 && c != a1 那么肯定有b - a1 - c

如果b != a2 && c != a2 那么肯定有b - a2 - c

否则假设 b != a1 && c != a2 那么肯定有b - a1 - a2 - c

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 2E5 + 20;
typedef long long LL;
const LL mo = 1000000007;

int n,X,Y,tp,Clock,tot,Ans = 1,cnt[maxn],last[maxn]
	,c[maxn],w[maxn],stk[maxn],Fac[maxn],Inv[maxn];
bool vis[maxn];

queue <int> Q; stack <int> s;
vector <int> v[maxn],g[maxn];

inline int Mul(const LL &x,const LL &y) {return x * y % mo;}
inline bool cmpw(const int &x,const int &y) {return w[x] < w[y];}
inline int C(const int &N,const int &M) {return Mul(Fac[N],Mul(Inv[M],Inv[N - M]));}

int ksm(int x,int y)
{
	int ret = 1;
	for (; y; y >>= 1)
	{
		if (y & 1) ret = Mul(ret,x);
		x = Mul(x,x);
	}
	return ret;
}

void BFS(int st)
{
	last[c[st]] = Clock; s.push(c[st]);
	vis[st] = cnt[c[st]] = 1; Q.push(st);
	while (!Q.empty())
	{
		int k = Q.front(); Q.pop(); ++tot;
		for (int i = 0; i < v[k].size(); i++)
		{
			int to = v[k][i]; if (vis[to]) continue;
			if (last[c[to]] == Clock) ++cnt[c[to]];
			else cnt[c[to]] = 1,last[c[to]] = Clock,s.push(c[to]);
			vis[to] = 1; Q.push(to);
		}
	}
}

void Add_Edgs(int st)
{
	for (int i = 1; i <= n; i++)
	{
		if (c[i] == c[st]) continue;
		if (w[i] + w[st] > Y) continue;
		v[st].push_back(i); v[i].push_back(st);
	}
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n >> X >> Y; Fac[0] = 1;
	for (int i = 1; i <= n; i++)
		scanf("%d%d",&c[i],&w[i]),g[c[i]].push_back(i);
	for (int i = 1; i <= n; i++) Fac[i] = Mul(Fac[i - 1],i);
	Inv[n] = ksm(Fac[n],mo - 2); for (int i = n - 1; i >= 0; i--) Inv[i] = Mul(Inv[i + 1],i + 1);
	for (int i = 1; i <= n; i++)
	{
		if (!g[i].size()) continue;
		sort(g[i].begin(),g[i].end(),cmpw);
		stk[++tp] = g[i][0];
		for (int j = 1; j < g[i].size(); j++)
		{
			if (w[g[i][0]] + w[g[i][j]] > X) break;
			v[g[i][0]].push_back(g[i][j]);
			v[g[i][j]].push_back(g[i][0]);
		}
	}
	sort(stk + 1,stk + tp + 1,cmpw);
	Add_Edgs(stk[1]); if (stk[2]) Add_Edgs(stk[2]);
	for (int i = 1; i <= n; i++)
	{
		if (vis[i]) continue; ++Clock; BFS(i);
		while (!s.empty())
		{
			int k = s.top(); s.pop();
			Ans = Mul(Ans,C(tot,cnt[k])); tot -= cnt[k];
		}
	}
	cout << Ans << endl;
	return 0;
}


E:

有n个绿洲排成一行,第i个绿洲的坐标为xi,有一只骆驼在这里旅行,他想遍历所有绿洲。它的驼峰初始体积为v并装有v的水。骆驼有两种移动方式:1.从位置xi走到位置xj并消耗abs(xi - xj)的水 2.如果此刻骆驼的驼峰剩余的水体积为v,v > 0,那么他可以选择一个坐标,瞬间移动过去,移动结束后驼峰的水量清空并且体积变为[v/2](向下取整)。当骆驼在一块绿洲上时,它可以无限补充自己的水量(显然不能超过驼峰体积),现在骆驼想知道,对于每一块绿洲,如果它从这里出发,是否能够遍历所有的绿洲。注意每块绿洲允许经过无限次,任何时刻骆驼驼峰内的水量不能为负数。2 <= n,v <= 2*10^5


solution:

因为操作2会减少驼峰体积,所以使用次数不超过logv。考虑选择一个起点出发,肯定是先向左向右尽量探索能到达的绿洲,然后再考虑使用瞬移。当两个绿洲之间的距离不超过驼峰的体积时显然是能够到达的,到达之后再补充水分即可。

预处理数组L[i][j]为从点i出发,已经跳过了j次,往左最远能走到哪个绿洲,类似地处理好R[i][j]。那么从起点i出发,记l = L[i][0],r = R[i][0],如果当前能够探索完所有绿洲,则一定存在一种将剩余次数分成两个不相交集合的方案,使得用其中一个集合能遍历1 ~ l - 1,另一个能遍历r + 1 ~ n

用预处理好的L,R数组做个dp就行了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int N = 20;
const int maxn = 2E5 + 20;

int n,v,Max,p[maxn],Min[maxn],Left[maxn][N],Right[maxn][N],f[1 << N],g[1 << N];

void Pre_Work(int k,int now)
{
	Left[1][k] = 1; Right[n][k] = n;
	for (int i = 2; i <= n; i++)
		Left[i][k] = p[i] - p[i - 1] <= now ? Left[i - 1][k] : i;
	for (int i = n - 1; i; i--)
		Right[i][k] = p[i + 1] - p[i] <= now ? Right[i + 1][k] : i;
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n >> v;
	for (int i = 1; i <= n; i++) scanf("%d",&p[i]);
	for (int x = v; ; x >>= 1)
	{
		Pre_Work(Max++,x);
		if (!x) break;
	}
	for (int i = 0; i <= n; i++) Min[i] = maxn; g[0] = n + 1;
	for (int o = 1; o < (1 << Max); o++)
	{
		g[o] = n + 1;
		for (int i = 0; i < Max; i++)
		{
			if (!(o & (1 << i))) continue;
			int op = o ^ (1 << i);
			f[o] = f[op] == n ? n : max(f[o],Right[f[op] + 1][i]);
			g[o] = g[op] == 1 ? 1 : min(g[o],Left[g[op] - 1][i]);
		}
	}
	for (int o = 0; o < (1 << Max); o++)
	{
		int A = o,B = ((1 << Max) - 1) ^ o;
		if (A & 1) A ^= 1; if (B & 1) B ^= 1;
		Min[f[A]] = min(Min[f[A]],g[B]);
	}
	for (int i = n - 1; i >= 0; i--) Min[i] = min(Min[i],Min[i + 1]);
	for (int i = 1; i <= n; i++)
		puts(Min[Left[i][0] - 1] <= Right[i][0] + 1 ? "Possible" : "Impossible");
	return 0;
}


F:

你有一个长度为2N - 1的数列a,第i个位置的数字是ai,现在,你可以把a中的数字随意排列,然后用这个新的数列a生成一个长度为N的数列b,具体的,bi = {a1,a2,a3,...,a2i-1}的中位数

询问一共有多少种可能的数列b。N <= 50,答案对10^9 + 7取模


solution:


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
 
const int N = 55;
const int M = 155;
typedef long long LL;
const LL mo = 1000000007;
 
int n,m,Ans,A[M],d[N],f[N][M][M];
bool Min[N],Max[N];
 
void Add(int &x,const int &y) {x += y; if (x > mo) x -= mo;}
 
int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n; m = 2 * n - 1;
	for (int i = 1; i <= m; i++) scanf("%d",&A[i]);
	sort(A + 1,A + m + 1); int mid = 1 + m >> 1;
	for (int i = 2; i <= n; i++)
	{
		if (A[mid - i + 1] != A[mid - i + 2]) ++d[i],Min[i] = 1;
		if (A[mid + i - 1] != A[mid + i - 2]) ++d[i],Max[i] = 1;
	}
	f[1][1][1] = 1;
	for (int i = 1; i < n; i++)
		for (int j = 1; j <= m; j++)
			for (int k = 1; k <= j; k++)
			{
				if (!f[i][j][k]) continue;
				int tot = j + d[i + 1];
				int pos = Min[i + 1] ? k + 1 : k;
				for (int l = 1; l < pos; l++)
					Add(f[i + 1][tot - (pos - l - 1)][l],f[i][j][k]);
				Add(f[i + 1][tot][pos],f[i][j][k]);
				for (int l = pos + 1; l <= tot; l++)
					Add(f[i + 1][tot - (l - pos - 1)][pos + 1],f[i][j][k]);
			}
	for (int i = 1; i <= m; i++)
		for (int j = 1; j <= i; j++)
			Add(Ans,f[n][i][j]);
	cout << Ans << endl;
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值