zoukankan      html  css  js  c++  java
  • AtCoder Grand Contest 046

    Preface

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

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


    A - Takahashikun, The Strider

    SB题目,显然最后要转的角度是$operatorname(X,360)(,因此答案就是)frac{operatorname(X,360)}=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 imes j$的矩形的方案数

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

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

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

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

    [ f_{i,j}=f_{i-1,j} imes j+f_{i,j-1} imes i-f_{i-1,j-1} imes (i-1) imes (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$(显然$kge 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 imes a_y$,那么显然存在某个$x$,$x$距离排列左右边界不存在$y$

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

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

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

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

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

    由于每次贪心的复杂度是$O(K)$的,总复杂度就是$O(K2 imes sum_ 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的说,感觉现在稍微有一点脑子了

  • 相关阅读:
    MySQL之LEFT JOIN中使用ON和WHRERE对表数据
    Mysql索引分类
    个人发展战略(二)
    个人发展战略(一)
    List的add方法与addAll方法的区别、StringBuffer的delete方法与deleteCharAt的区别
    职业理财规划
    Servlet简介与Servlet和HttpServlet运行的流程
    Ajax的get、post和ajax提交
    Ajax方法
    监听器随笔
  • 原文地址:https://www.cnblogs.com/cjjsb/p/13368953.html
Copyright © 2011-2022 走看看