Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)

Preface

这场比赛的时候 CF 一直抽风,快 10min 的时候才登上去,然后中间交题也一直在人机验证

50min 左右过了前 5 题后,F 没想到生成树当场趋势了,赛后发现原来是原题大战可海星


A. Diverse Game

将每个数循环移位变为下一个数即可,注意特判 \(n=m=1\) 的情形

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=15;
int t,n,m,a[N][N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; scanf("%d%d",&n,&m); int all=n*m;
		for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&a[i][j]),a[i][j]=a[i][j]%all+1;
		if (n*m==1) { puts("-1"); continue; }
		for (i=1;i<=n;++i) for (j=1;j<=m;++j) printf("%d%c",a[i][j]," \n"[j==m]);
	}
	return 0;
}

B. Fun Game

手玩一下发现只要前面存在一个 \(1\),那么后面任意位置都可以任意取值

因此这个题实际上就是比较两个串第一个 \(1\) 出现的位置

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005;
int t,n; char a[N],b[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; scanf("%d%s%s",&n,a+1,b+1);
		auto fst=[&](char *s)
		{
			for (RI i=1;i<=n;++i) if (s[i]=='1') return i;
			return n+1;
		};
		puts(fst(a)<=fst(b)?"YES":"NO");
	}
	return 0;
}

C. Hungry Games

考虑倒着递推,令 \(f_i\) 表示以 \(i\) 为左端点的合法区间数量

转移的话考虑找到最靠前的 \(j\),使得 \(\sum_{k=i}^j a_k>x\),这个可以预处理前缀和数组然后二分求解

根据定义显然有 \(f_i=f_{j+1}+1\),注意特判 \(j\) 不存在的情况

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005;
int t,n,m,x,pfx[N],f[N];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld%lld",&n,&m),i=1;i<=n;++i)
		scanf("%lld",&x),pfx[i]=pfx[i-1]+x; int ans=0;
		for (f[n+1]=0,i=n;i>=1;--i)
		{
			int pos=upper_bound(pfx+i,pfx+n+1,m+pfx[i-1])-pfx;
			if (pos<=n) f[i]=f[pos+1]+1; else f[i]=0;
			ans+=f[i];
		}
		printf("%lld\n",n*(n+1)/2-ans);
	}
	return 0;
}

D. Funny Game

感觉是本场最有意思的一个题了

不难发现边权越小的边可供连接的点对越多,因此我们从点对较少的边权大的边开始处理

考虑连接边权为 \(n-1\) 的边时,我们可以将所有点按照点权对 \(n-1\) 的余数分为 \(n-1\) 类,根据鸽笼原理,总是存在两个及以上的点在一类中

任意选出这样的两个点后,再它们之间连上边,然后随便删掉一个点,不难发现问题被规约为更小规模的原问题,因此可以一直往前做到边权为 \(1\)

我比赛的时候抽风了没有直接把点删了而是用并查集合并起来,每次要找两个在不同联通块的点连边,相比之下比较麻烦

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=2005;
int t,n,a[N],fa[N],lst_fa[N],num[N]; vector <int> vec[N];
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
		scanf("%d",&a[i]),fa[i]=i;
		vector <pi> ans;
		for (i=n-1;i>=1;--i)
		{
			for (j=0;j<i;++j) lst_fa[j]=num[j]=-1;
			for (j=1;j<=n;++j) 
			{
				int pos=a[j]%i;
				if (lst_fa[pos]==-1)
				{
					lst_fa[pos]=getfa(j);
					num[pos]=j; continue;
				}
				if (lst_fa[pos]!=getfa(j))
				{
					ans.push_back(pi(num[pos],j));
					fa[getfa(num[pos])]=getfa(j); break;
				}
			}
		}
		puts("YES"); reverse(ans.begin(),ans.end());
		for (auto [x,y]:ans) printf("%d %d\n",x,y);
	}
	return 0;
}

E. Wooden Game

究极丁真题,不难发现点数最大的那棵树显然全部删去是最优的,现在我们只需要考虑尽可能地将它二进制下为 \(0\) 的那些位变成 \(1\) 即可

考虑对于一棵 \(x\) 个点的树,由于我们总可以删去某个叶子使得其大小减 \(1\),因此实际上它能贡献 \([1,x]\) 中的任意一个值

故而这题的树的形态就是花椒粉(徐神语:即诈骗用的),直接贪心处理即可

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=1e6+5;
int t,n,a[N],x;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
		for (scanf("%d",&a[i]),j=1;j<a[i];++j) scanf("%d",&x);
		sort(a+1,a+n+1); int ans=a[n]; vector <int> ufd;
		int hb=0; for (i=0;i<20;++i) if ((ans>>i)&1) hb=i;
		for (i=hb;i>=0;--i) if (((ans>>i)&1)==0) ufd.push_back(i);
		for (i=n-1;i>=1;--i)
		{
			vector <int> tmp;
			for (auto x:ufd) if (a[i]>=(1<<x)) a[i]-=(1<<x),ans|=(1<<x);
			else tmp.push_back(x); ufd=tmp;
		}
		printf("%d\n",ans);
	}
	return 0;
}

F. Stardew Valley

ORZ 徐神一眼秒杀此题,但不会写欧拉回路板子可海星

首先题目就是要找经过所有有 NPC 的边(以下称为关键边)的欧拉回路,由于题目中已经保证依靠关键边可以将图连通,因此我们只需要用没有 NPC 的边(以下称为非关键边)把每个点的度数补为偶数即可

考虑将仅考虑关键边时,度数为奇数的点称为关键点,现在删去图中所有关键边,仅考虑非关键边,显然每个联通块独立

发现若一个联通块内有奇数个关键点则显然无解,因为此时需要奇数次度数变化,而一条边带来的度数变化是偶数,因此必然无解

否则有偶数个关键点时我们总有如下的构造方案,即先对该联通块求出任一生成树,再将所有关键点任意两两配对,将配对的两点间树上路径的选择关系取反,最后将要选择的边加入跑欧拉回路即可

上述做法的正确性在于树上一条路径端点被边 cover 一次,中间其余的点被边 cover 两次,因此满足度数要求

实现时不需要真的维护树上路径,只需要根据子树内关键点的数量决定该点的父边是否要选即可

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=500005;
int t,n,m,x[N],y[N],c[N],d[N],deg[N],vis[N],used[N]; vector <pi> v[N],tr[N],nv[N];
inline void DFS1(vector <int>& odd,CI now,CI fa=0)
{
	if (deg[now]%2==1) odd.push_back(now); vis[now]=1;
	for (auto [to,id]:v[now]) if (to!=fa&&!vis[to])
	tr[now].push_back(pi(to,id)),DFS1(odd,to,now);
}
inline void DFS2(CI now,CI fa=0,CI lst=0)
{
	for (auto [to,id]:tr[now]) if (to!=fa) DFS2(to,now,id),d[now]^=d[to];
	if (d[now]) nv[now].push_back(pi(fa,lst)),nv[fa].push_back(pi(now,lst));
}
inline void Euler(vector <int>& path,CI now=1)
{
	while (!nv[now].empty())
	{
		auto [to,id]=nv[now].back(); nv[now].pop_back();
		if (used[id]) continue; used[id]=1; Euler(path,to);
	}
	path.push_back(now);
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
		{
			scanf("%d%d%d",&x[i],&y[i],&c[i]); used[i]=0;
			if (c[i]==0)
			{
				v[x[i]].push_back(pi(y[i],i));
				v[y[i]].push_back(pi(x[i],i));
			} else
			{
				++deg[x[i]]; ++deg[y[i]];
				nv[x[i]].push_back(pi(y[i],i));
				nv[y[i]].push_back(pi(x[i],i));
			}
		}
		bool flag=1; for (i=1;i<=n;++i) if (!vis[i])
		{
			vector <int> odd; DFS1(odd,i);
			if (odd.size()%2==1) { flag=0; break; }
			for (auto x:odd) d[x]^=1; DFS2(i);
		}
		if (!flag) puts("NO"); else
		{
			puts("YES"); vector <int> path;
			Euler(path); reverse(path.begin(),path.end());
			printf("%d\n",path.size()-1);
			for (auto x:path) printf("%d ",x); putchar('\n');
		}
		for (i=1;i<=n;++i) deg[i]=d[i]=vis[i]=0,v[i].clear(),tr[i].clear(),nv[i].clear();
	}
	return 0;
}

G. Minecraft

也是个丁真题,没想到这么煞笔的题放在这个位置

考虑从低位向高位 DP,令 \(f_{i,j}\) 表示处理了前 \(i\) 位,此时从低位来的进位为 \(j\) 的局面是否存在

注意到假设所有低位全为 \(1\),进位也不会超过 \(\frac{n}{2}+\frac{n}{4}+\frac{n}{8}+\dots=n\),因此第二位的上界为 \(n\)

每位的决策只有两种,即 \(x\) 这一位取 \(0/1\),分别讨论一下即可,复杂度 \(O(nk)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=2e6+5;
int t,n,k; char str[N];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; scanf("%d%d",&n,&k);
		vector <int> cnt(k),s(k);
		for (scanf("%s",str),i=0;i<k;++i) s[k-1-i]=str[i]-'0';
		for (i=0;i<n;++i)
		for (scanf("%s",str),j=0;j<k;++j) cnt[k-1-j]+=str[j]-'0';
		vector <vector <int>> f(k+1,vector <int>(n+1));
		vector <vector <pi>> pre(k+1,vector <pi>(n+1));
		for (f[0][0]=1,i=0;i<k;++i) for (j=0;j<=n;++j)
		{
			if (!f[i][j]) continue;
			if ((cnt[i]+j)%2==s[i])
			{
				f[i+1][(cnt[i]+j)/2]=1;
				pre[i+1][(cnt[i]+j)/2]={0,j};
			}
			if ((n-cnt[i]+j)%2==s[i])
			{
				f[i+1][(n-cnt[i]+j)/2]=1;
				pre[i+1][(n-cnt[i]+j)/2]={1,j};
			}
		}
		if (!f[k][0]) { puts("-1"); continue; }
		for (int cur=k,lst=0;cur>=1;--cur)
		putchar(pre[cur][lst].fi+'0'),lst=pre[cur][lst].se;
		putchar('\n');
	}
	return 0;
}

Postscript

这场题好抽象啊,难度感觉被 Random_Shuffle 了一样,打得人好难受

posted @ 2024-07-21 19:40  空気力学の詩  阅读(13)  评论(0编辑  收藏  举报