Codeforces Round 868 (Div. 2)

Preface

恭迎废物hl666终于回到了他忠诚的蓝名之位

本来这场25min切完前三题而且没挂的时候感觉状态不错的,然后D被自己的一个假做法坑了1h

最后ztc想出了大概的构造方法后已经来不及写了,一些细节没考虑好直接爆炸

本来当时就有弃D开E的想法的,但可惜E的题意在公告出来之前就已经读错了,后面出了公告的时候已经决定死磕D了就没管了

不然我是打算先写E的暴力找规律的,因为前面的假题意发现连暴力都写不来

嘛不管怎么说还是自己太菜了,滚回去也是正常的说,只能再多加练习了的的说


A. A-characteristic

SB题,枚举\(1\)的个数判断即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,k,a[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; int ret=-1; for (scanf("%d%d",&n,&k),i=0;i<=n;++i)
		if (i*(i-1)/2LL+(n-i)*(n-i-1)/2LL==k) { ret=i; break; }
		if (!~ret) { puts("NO"); continue; }
		for (puts("YES"),i=1;i<=ret;++i) putchar('1'),putchar(' ');
		for (i=ret+1;i<=n;++i) printf("-1"),putchar(' '); putchar('\n');
	}
	return 0;
}

B. Sort with Step

SB题,不难发现此时的交换只能在下标对\(k\)取模的同余系中进行

因此把每个数初始时所在的下标和最后要换到的位置的下标对\(k\)取模的值比较下,统计出不相同的对数\(cnt\)

显然若\(cnt>2\)则一定无解,否则\(cnt=0\)则直接合法,\(cnt=2\)则交换一次后有解

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,k,x,pos[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i)
		scanf("%d",&x),pos[x]=i; int cur=0,x,y;
		for (i=1;i<=n;++i) if (pos[i]%k!=i%k) ++cur;
		if (cur>2) { puts("-1"); continue; }
		if (cur==0) { puts("0"); continue; }
		puts("1");
	}
	return 0;
}

C. Strongly Composite

一眼题,首先考虑先把所有数都质因数分解,求出每个质因数\(p_i\)的次数\(c_i\)

不难发现贪心地把\(c_i\)拿出两个组成一个数是合法且最优的,然后此时我们就剩下了一些次数为\(1\)余量

考虑不同的质数之间至少要\(3\)个才能组成一个合法的数,因此把剩下的三三分组即可

由于多组数据下清空数组很麻烦,所以建议直接开unordered_map

#include<cstdio>
#include<iostream>
#include<unordered_map>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5;
int t,n,x,pri[N],cnt; bool vis[N]; unordered_map <int,int> rst;
inline void init(CI n)
{
	RI i,j; for (i=2;i<=n;++i)
	{
		if (!vis[i]) pri[++cnt]=i;
		for (j=1;j<=cnt&&i*pri[j]<=n;++j)
		if (vis[i*pri[j]]=1,i%pri[j]==0) break;
	}
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t),init(1e7);t;--t)
	{
		RI i,j; for (scanf("%d",&n),rst.clear(),i=1;i<=n;++i)
		{
			for (scanf("%d",&x),j=1;j<=cnt&&pri[j]*pri[j]<=x;++j)
			if (x%pri[j]==0)
			{
				while (x%pri[j]==0) ++rst[pri[j]],x/=pri[j];
			}
			if (x>1) ++rst[x];
		}
		int ret=0,left=0; for (auto [u,v]:rst) ret+=v/2,left+=v%2;
		printf("%d\n",ret+left/3);
	}
	return 0;
}

D. Unique Palindromes

构造题腐乳闪总出列,再扯什么构造题精通我就是弱智,这下被狠狠地拷打了

首先我们要发现两个重要性质:长度为\(k\)的串最多只能有\(k\)个本质不同的回文串;只用两种字符时对于长度为\(k\)的串本质不同的回文串个数永远是\(k\)

但是我们会发现如果有三种字符结果就大不一样了,\(abcabcabc\cdots\)这个串不管长度为多少答案都是\(3\)

然后我们就由此想到一种构造原则,考虑构造的方案中回文串只能由一种字符构成,即等价于任意一种字符中间都要有两种其它的字符

再注意到一点就是\(k\)的值很小,小到比字符集大小还小,因此很容易想到每次新引入一种字符来统计次数,然后就有了大致的做法

先用\(abc\)三种字符填充第一个限制,这里可以让某个前缀字符出现次数多点来得到更多的答案,比如\(x_1=6,c_1=4\)就有构造\(aaaabc\)

然后在后面我们考虑把\(abc\)当作分隔符来用,先填充一段\(c_i-c_{i-1}\)长度的新字符后,后面就用\(abc\)的循环来填即可

不过要注意上面所提的那个要求,因此要判断下以哪个字符开头循环,具体实现可以看代码

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,M=25;
int T,n,k,x[M],c[M]; char s[N],t[3];
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,&k); 
		for (i=1;i<=k;++i) scanf("%d",&x[i]);
		for (i=1;i<=k;++i) scanf("%d",&c[i]);
		bool flag=1; for (i=1;i<=k&&flag;++i) if (x[i]<c[i]) flag=0;
		if (!flag) { puts("NO"); continue; }
		auto solve=[&](CI l,CI r)
		{
			for (RI i=0;i<=r-l;++i) s[l+i]=t[i%3];
		};
		for (i=1;i<=c[1]-2;++i) s[i]='a';
		t[0]='b'; t[1]='c'; t[2]='a'; solve(c[1]-1,x[1]);
		for (i=2;i<=k;++i)
		{
			int dx=x[i]-x[i-1],dc=c[i]-c[i-1];
			if (dx<dc) { flag=0; break; }
			if (!dc)
			{
				auto find=[&](CI pos,const char& ch)
				{
					for (RI i=pos;i;--i) if (s[i]!=ch) return i;
				};
				if (s[x[i-1]]!='a'&&s[find(x[i-1]-1,s[x[i-1]])]!='a')
				t[0]='a',t[1]='b',t[2]='c'; else
				if (s[x[i-1]]!='b'&&s[find(x[i-1]-1,s[x[i-1]])]!='b')
				t[0]='b',t[1]='c',t[2]='a';	else
				t[0]='c',t[1]='a',t[2]='b';
				if (s[x[i-1]]==t[1]) swap(t[1],t[2]);
				solve(x[i-1]+1,x[i]); continue;
			}
			for (j=1;j<=dc;++j) s[x[i-1]+j]='c'+i-1;
			if (s[x[i-1]]!='a')
			t[0]='a',t[1]='b',t[2]='c'; else
			if (s[x[i-1]]!='b')
			t[0]='b',t[1]='c',t[2]='a';	else
			t[0]='c',t[1]='a',t[2]='b';
			if (s[x[i-1]]==t[1]) swap(t[1],t[2]);
			solve(x[i-1]+dc+1,x[i]);
		}
		if (!flag) { puts("NO"); continue; }
		for (puts("YES"),i=1;i<=n;++i) putchar(s[i]); putchar('\n');
	}
	return 0;
}

E. Removing Graph

很经典的博弈模型,一眼对不同大小的环求SG函数,但是前面题意看假了暴力都写不来苦路西

后面发现每次只能取一段连续的就可以写暴力了,然后这个的规律其实还是挺好找的说

先说下暴力的写法吧,设\(cycle[x]\)表示长度为\(x\)的环的SG函数,\(chain[x]\)表示长度为\(x\)的链的SG函数,转移有:

  • \(cycle[x]=\operatorname{mex}_{l\le i\le r} chain[x-i]\)
  • \(chain[x]=\operatorname{mex}_{l\le i\le r,1\le j\le x,i+j-1\le x} chain[j-1]\oplus chain[x-(i+j-1)]\)

暴力求SG函数的代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=35;
int n,l,r,cycle[N],chain[N];
inline int Chain(CI x)
{
	if (~chain[x]) return chain[x];
	bool vis[N]; memset(vis,0,sizeof(vis));
	for (RI i=1;i<=x;++i) for (RI j=l;j<=r;++j)
	if (i+j-1<=x) vis[Chain(i-1)^Chain(x-(i+j-1))]=1;
	int mex=0; while (vis[mex]) ++mex;
	return chain[x]=mex;
}
inline int Cycle(CI x)
{
	if (~cycle[x]) return cycle[x];
	bool vis[N]; memset(vis,0,sizeof(vis));
	for (RI i=l;i<=r;++i) if (i<=x) vis[Chain(x-i)]=1;
	int mex=0; while (vis[mex]) ++mex;
	return cycle[x]=mex;
}
int main()
{
	freopen("CODE.out","w",stdout);
	for (l=1;l<=15;++l) for (r=l+1;r<=15;++r)
	{
		RI i; printf("Case %d %d\n",l,r);
		memset(cycle,-1,sizeof(cycle));	memset(chain,-1,sizeof(chain));
		for (printf("index "),i=1;i<=30;++i) printf("%-4d%c",i," \n"[i==30]);
		for (printf("chain "),i=1;i<=30;++i) printf("%-4d%c",Chain(i)," \n"[i==30]);
		for (printf("cycle "),i=1;i<=30;++i) printf("%-4d%c",Cycle(i)," \n"[i==30]);
	}
	return 0;
}

然后随便截取一段暴力的SG函数就可以发现规律:

  • \(x\ge l+r\)时,\(cycle[x]=0\)
  • \(x<l+r\)时,\(cycle[x]=\lfloor \frac{x}{l}\rfloor\)

而关于这个东西是如何推导得出的就有些复杂了,可以去Tutorial

最后代码就很好写了

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,l,r,x,y,sz[N],ret; vector <int> v[N]; bool vis[N];
inline void DFS(CI now)
{
	sz[now]=vis[now]=1; for (int to:v[now]) if (!vis[to]) DFS(to),sz[now]+=sz[to];
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d%d",&n,&l,&r),i=1;i<=n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	auto SG=[&](CI x)
	{
		if (x>=l+r) return 0; return x/l;
	};
	for (i=1;i<=n;++i) if (!vis[i]) DFS(i),ret^=SG(sz[i]);
	return puts(ret?"Alice":"Bob"),0;
}

F. Random Walk

做不来的期望题捏,现在这类题遇到就寄感觉只能躺好等队友了捏

首先不妨先令\(e(t)=0\),这样推出的式子形式比较统一,最后输出的时候补成\(1\)就行

一个naive的想法就是对于每个点列出转移方程,显然有:

\[e(x)=\sum_{(x,y)\in E} \frac{e(y)}{deg(y)} \]

但直接做高斯消元肯定是不可能的,我们考虑寻找一点特殊性

考虑问题的简化版本,对于一条链,起点\(s=1\),终点\(t=n\),它的解该如何构造呢

经过简单地尝试我们很容易得到答案为\(e(1)=n-1,e(2)=2(n-2),e(3)=2(n-3),\cdots,e(n-1)=2,e(n)=0\)

把这个式子调整下就变成了\(e(i)=(n-i)\times deg(i)\),然后考虑怎么把原来树上的问题转化到链上

显然由于树的优秀性质,\(s\)\(t\)的路径一定是唯一的,然后如果我们找出这条路径后,可以以上面的点为根让剩下的点都在某个有根树中

这么做的意义何在呢,这里就有个性质,对于树上某点\(v\)与其父亲\(p\),一定有\(e(u)=\frac{deg(u)}{deg(p)}\cdot e(p)\),证明如下:

  • \(u\)为叶子节点,则结论显然成立
  • 否则有\(e(u)=\sum_\limits{(u,v)\in E\and v\ne p} \frac{e(v)}{deg(v)}+\frac{e(p)}{deg(p)}=\sum_\limits{(u,v)\in E\and v\ne p} \frac{deg(v)\cdot e(u)}{deg(v)\cdot dep(u)}+\frac{e(p)}{deg(p)}=\frac{deg(u)-1}{deg(u)}\cdot e(u)+\frac{e(p)}{deg(p)}\),则有\(e(u)=\frac{deg(u)}{deg(p)}\cdot e(p)\)

然后我们发现这个式子有一个很好的连乘性质,此时以\(r_i\)为根的子树内所有点\(t\)的答案就是\(e(t)=\frac{deg(t)}{deg(r_i)}\cdot e(r_i)\)

此时我们用前面的式子\(e(i)=(n-i)\times deg(i)\)算出路径上所有点的答案然后再下传到子树即可,经过检验这确实是原方程的解

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=998244353;
int n,s,t,x,y,pre[N],coef[N],E[N]; bool path[N]; vector <int> v[N];
inline void DFS1(CI now,CI fa=0)
{
	if (now==t) return; for (int to:v[now]) if (to!=fa) pre[to]=now,DFS1(to,now);
}
inline void DFS2(CI now,CI fa,CI mul)
{
	E[now]=1LL*mul*v[now].size()%mod;
	for (int to:v[now]) if (to!=fa) DFS2(to,now,mul);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d%d",&n,&s,&t),i=1;i<n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	DFS1(s); path[t]=1; for (int now=t;now!=s;now=pre[now])
	coef[pre[now]]=coef[now]+1,path[pre[now]]=1;
	for (i=1;i<=n;++i) if (path[i])
	{
		for (int to:v[i]) if (!path[to]) DFS2(to,i,coef[i]);
		E[i]=1LL*coef[i]*v[i].size()%mod;
	}
	for (++E[t],i=1;i<=n;++i) printf("%d ",E[i]);
	return 0;
}

Postscript

今天晚上还有场Div2,必须一雪前耻回到紫名

不过学校的图论专题好像也是凌晨开放,如果到时候清醒的话可以抢几个一血再睡觉的说

posted @ 2023-04-29 10:45  空気力学の詩  阅读(104)  评论(0编辑  收藏  举报