AtCoder Grand Contest 046

Preface

话说这场前面的题好简单啊,而且题意都很好懂,争取全写了吧(flag)

7/25:好吧F好仙各种竞赛图的引理定理弃疗了QAQ


A - Takahashikun, The Strider

SB题目,显然最后要转的角度是\(\operatorname{lcm}(X,360)\),因此答案就是\(\frac{\operatorname{lcm}(X,360)}{x}=\frac{360}{\gcd(X,360)}\)

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int x;
inline int gcd(CI x,CI y)
{
	return y?gcd(y,x%y):x;
}
int main()
{
	return scanf("%d",&x),printf("%d",360/gcd(x,360)),0;
}


B - Extension

首先设状态,很显然可以设\(f_{i,j}\)表示\(i\times j\)的矩形的方案数

首先考虑边界情况,转移十分显然:

\[f_{i,b}=f_{i-1,b}\times b\\ f_{a,i}=f_{a,i-1}\times a \]

考虑一般情况,一个矩形一定是之前的某个矩形加一行或一列得到的,如果直接把方案加起来显然会有重复

然后我们稍微想一下就会发现不重复的情况仅有占据了交点时,换句话说其他的情况都有一次重复,容易写出转移:

\[f_{i,j}=f_{i-1,j}\times j+f_{i,j-1}\times i-f_{i-1,j-1}\times (i-1)\times (j-1) \]

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005,mod=998244353;
int a,b,c,d,f[N][N];
int main()
{
	RI i,j; scanf("%d%d%d%d",&a,&b,&c,&d); f[a][b]=1;
	for (i=a;i<=c;++i) for (j=b;j<=d;++j)
	if (i>a&&j>b) f[i][j]=(1LL*f[i-1][j]*j%mod+1LL*f[i][j-1]*i%mod-1LL*(i-1)*(j-1)*f[i-1][j-1]%mod+mod)%mod;
	else if (i>a) f[i][j]=1LL*f[i-1][j]*j%mod; else if (j>b) f[i][j]=1LL*f[i][j-1]*i%mod;
	return printf("%d",f[c][d]),0;
}


C - Shift

首先转化题意,预处理出一个数组\(a\)表示每两个\(0\)之间\(1\)的个数

那么此时一次操作可以看成\((i,j).i<j\),表示\(a_i+=1\)\(a_j-=1\)

容易发现当操作\((i,k),(k,j)\)后相当于进行了操作\((i,j)\),由于我们要使操作尽量少,因此我们强制不会有前面这种情况出现,换句话说就是令每个位置要么加要么减

因此我们容易设计出一个状态,\(f_{i,j,k}\)表示进行了\(i\)次操作,处理了前\(j\)个位置,加的次数与减的次数的差值为\(k\)(显然\(k\ge 0\))的方案数

转移的话很简单,考虑这个位置要么不做,要么加要么减即可,注意减的时候不要减得超过这个数,具体看代码

然后写完一看是\(O(n^4)\)的,压一下上界还是两个点过不去怎么办

发现这个DP有效状态很少,因此把填表改成刷表后就快得一批,直接1500ms跑过

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=305,mod=998244353;
char s[N]; int n,m,t,lim,a[N],f[N<<1][N][N<<1],ans; //f[x][y][z]: x turns; position y; delta z
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	RI i,j,k,p; for (scanf("%s%d",s+1,&t),n=strlen(s+1),m=i=1;i<=n;++i)
	if (s[i]=='0') ++m; else ++a[m]; if (!a[m]) --m;
	f[0][0][0]=1; lim=(min(t,n)<<1);
	for (j=0;j<m;++j) for (i=0;i<=lim;++i) for (k=0;k<=n;++k) if (f[i][j][k])
	{
		inc(f[i][j+1][k],f[i][j][k]); //do nothing
		for (p=1;i+p<=lim;++p) inc(f[i+p][j+1][k+p],f[i][j][k]); //addition
		for (p=1;p<=min(k,a[j+1])&&i+p<=lim;++p) inc(f[i+p][j+1][k-p],f[i][j][k]); //subtraction
	}
	for (i=0;i<=(min(t,n)<<1);++i) inc(ans,f[i][m][0]);
	return printf("%d",ans),0;
}


D - Secret Passage

该开始推的DP忘记判状态是否合法了,直接GG

首先我们发现可以把操作倒过来,考虑一个串\(T\)变回\(S\)的过程,每次选一个之前的字符扔到最前面,并且生成一个新的字符

考虑哪些字符会被扔到前面去,考虑从后往前匹配,显然是要删除尽量少的字符满足剩下的是\(S\)的后缀

那么我们可以得出一个状态,\(f_{i,j,k}\)表示长度为\(i\)的串去匹配\(S\)的后缀,有\(j\)\(0\)\(k\)\(1\)要扔到前面去的方案数

DP的转移非常简单,考虑这个位置要么相同(不扔)要么不同(扔)\(O(1)\)转移即可

但是我们仔细一想我们这样得到的状态无法得知哪些\(0/1\)串能被生成,因此我们考虑设状态\(vis_{i,j,k}\)表示以上的状态能否生成原串,只不过要从前往后考虑,能否给后面的字符插入\(0\)\(1\)

转移的情况有四种,忽略或者和之后的一个配对(因为都可以保留),或者和之前凑好的\(0/1\)配对

最后累加上\(vis_{i,j,k}=1\)\(f_{i,j,k}\)即可

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=305,mod=998244353;
char s[N]; int n,f[N][N][N],ans; bool vis[N][N][N]; // f[i][j][k]: length i; j 0s to add; k 1s to add
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	RI i,j,k; scanf("%s",s+1); n=strlen(s+1); s[0]='2';
	for (f[0][0][0]=1,i=0;i<n;++i) for (j=0;j<=i;++j)
	for (k=0;j+k<=i;++k) if (f[i][j][k])
	{
		int c=s[n-(i-j-k)]-'0'; inc(f[i+1][j][k],f[i][j][k]);
		if (c) inc(f[i+1][j+1][k],f[i][j][k]); else inc(f[i+1][j][k+1],f[i][j][k]);
	}
	for (vis[0][0][0]=vis[n][0][0]=1,i=n-1;i;--i)
	for (j=0;j<=i;++j) for (k=0;j+k<=i;++k)
	{
		int c1=s[n-(i-j-k)]-'0',c2=s[n-(i-j-k)-1]-'0',t[3]; t[2]=0;
		if (t[0]=j,t[1]=k,++t[0],t[c1])
		{
			if (--t[c1],t[c2]&&(c1==0||c2==0)) --t[c2];
		}
		vis[i][j][k]|=vis[i+1][t[0]][t[1]];
		if (t[0]=j,t[1]=k,++t[1],t[c1])
		{
			if (--t[c1],t[c2]&&(c1==1||c2==1)) --t[c2];
		}
		vis[i][j][k]|=vis[i+1][t[0]][t[1]];
	}
	for (i=1;i<=n;++i) for (j=0;j<=i;++j) for (k=0;j+k<=i;++k)
	if (vis[i][j][k]) inc(ans,f[i][j][k]); return printf("%d",ans),0;
}


E - Permutation Cover

首先我们考虑判掉无解的情况,考虑如果有两个元素\(x,y\),满足\(a_x>2\times a_y\),那么显然存在某个\(x\)\(x\)距离排列左右边界不存在\(y\)

因此我们可以找出排列中的最大值和最小值进行无解的判断

考虑有了这个性质之后怎么构造答案,由于字典序最小显然可以贪心增量,考虑现在的序列为\(P\),满足\(P\)是某个排列的前缀

容易发现我们可以记录\(b_i\)表示每个数剩下多少,考虑什么情况才满足条件:

  • \(x,y\)分别为\(b_i\)\(\max\)\(\min\),要求满足\(x\le 2\times y+1\)(这里可以加\(1\)是因为左边界的条件被改变了)
  • \(x=2\times y+1\),则要求\(P\)结尾\(K\)个元素构成的排列满足所有\(b_i=x\)的元素都在\(b_i=y\)的元素之前(画图理解一下,证明和上面类似)

由于此时我们需要\(P\)时刻满足题意,因此无法一个一个的加入元素,而我们每次可以枚举加入\(i\)元素,将\(P\)结尾的\(K-i\)个元素和这\(i\)个构成排列,然后贪心得到字典序最小的增量即可

由于每次贪心的复杂度是\(O(K)\)的,总复杂度就是\(O(K^2\times \sum_{i=1}^ K a_i)\)

#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
#define RI register int
#define CI const int&
#define pb push_back
using namespace std;
const int N=1005,K=105;
using namespace std;
int k,n,a[K],b[K],ans[N]; vector <int> q[K]; bool vis[K];
inline bool cmp(vector <int> A,vector <int> B)
{
	for (RI i=0;i<A.size()&&i<B.size();++i)
	if (A[i]!=B[i]) return A[i]<B[i]; return A.size()<B.size();
}
inline void fail(vector <int>& v)
{
	for (RI i=1;i<=k;++i) v.pb(k+1);
}
inline void solve(CI st,CI num,vector <int>& res)
{
	res.clear(); memset(vis,0,sizeof(vis)); memcpy(b,a,sizeof(b));
	RI i; for (i=1;i+num<=k;++i) vis[ans[st-i]]=1;
	for (i=1;i<=k;++i) if (!vis[i])
	{
		if (b[i]) --b[i]; else return fail(res);
	}
	int x=*max_element(b+1,b+k+1),y=*min_element(b+1,b+k+1);
	if (x>2*y+1) return fail(res); vector <int> A,B,C; //A:b[i]=x; B:b[i]=y; C:otherwise
	if (x==2*y+1)
	{
		bool exist=0; for (i=1;i+num<=k;++i) 
		if (b[ans[st-i]]==x) exist=1; else if (b[ans[st-i]]==y&&exist) return fail(res);
		for (i=k;i;--i) if (!vis[i])
		{
			if (b[i]==x) A.pb(i); else if (b[i]==y) B.pb(i); else C.pb(i);
		}
	} else for (i=k;i;--i) if (!vis[i]) C.pb(i);
	for (i=1;i<=num;++i) if (A.size())
	{
		int x=C.size()?C.back():k+1; if (A.back()<x) res.pb(A.back()),A.pop_back();
		else res.pb(x),C.pop_back();
	} else
	{
		int x=B.size()?B.back():k+1,y=C.size()?C.back():k+1;
		if (x<y) res.pb(x),B.pop_back(); else res.pb(y),C.pop_back();
	}
}
int main()
{
	RI i,j; for (scanf("%d",&k),i=1;i<=k;++i) scanf("%d",&a[i]),n+=a[i];
	if (*max_element(a+1,a+k+1)>2*(*min_element(a+1,a+k+1))) return puts("-1"),0;
	int num; for (i=1;i<=n;i+=q[num].size())
	{
		if (i==1) solve(i,k,q[num=1]); else
		{
			for (j=1;j<=k;++j) solve(i,j,q[j]); num=1;
			for (j=2;j<=k;++j) if (cmp(q[j],q[num])) num=j;
		}
		for (j=0;j<q[num].size();++j) ans[i+j]=q[num][j],--a[q[num][j]];
	}
	for (i=1;i<=n;++i) printf("%d ",ans[i]); return 0;
}


Postscript

不知不觉打了挺多AGC的说,感觉现在稍微有一点脑子了

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/13368953.html