• 【AtCoder】AtCoder Grand Contest 031 解题报告($Asim E$)


    点此进入比赛

    (A):Colorful Subsequence(点此看题面

    大致题意: 给定一个序列,问有多少个子序列满足子序列中的字符各不相同。

    刚看完题面差点以为自己连(A)题都不会了,但仔细一想发现果然是道送分题。

    (f_i)表示在前(i)个字符中进行选择的答案,并在枚举(i)的同时用(c_x)表示(x)在前(i-1)个字符中的出现次数。

    考虑在(f_{i-1})中,是否选择(s_i)会有(c_{s_i}+1)种情况(选择了(c_{s_i})个中的一个,或是不选),而这并不会影响其他字符的选择。

    也就是说,没有选择过(s_i)的方案数是(frac1{c_{s_i}+1} imes f_{i-1})

    那么实际上完全不需要开数组,直接用一个变量(f),每次令(f=f imes(frac1{c_{s_i}+1}+1)),并给(c_{s_i})(1)就可以了。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define X 1000000007
    using namespace std;
    int n,c[30];char s[N+5];
    I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    int main()
    {
    	RI i,f=1;for(scanf("%d%s",&n,s+1),i=1;i<=n;++i) f=1LL*f*(QP(c[s[i]&31]+1,X-2)+1)%X,++c[s[i]&31];//递推转移
    	return printf("%d
    ",f-1),0;//减去空串的情况
    }
    

    (B):Reversi(点此看题面

    大致题意: 给定一个序列,可进行任意次操作,每次选择(i,j)满足(a_i=a_j)([i,j])的数都变成(a_i)。求可能得到多少种不同的序列。

    同样的(SB)题,很容易想到两个结论:

    • 对于(i),只需考虑上一个(a_{lst}=a_i)(lst)就可以了,因为大的区间完全可以分成若干个小区间。
    • 一种可能的序列必然存在一种生成方式,满足每一个数最多被改变一次。

    于是我们设(f_i)表示在前(i)个数中可能得到的序列数,(lst)表示上一个(a_{lst}=a_i)(lst),转移方程显然:

    [f_i=f_{i-1}+f_{lst} ]

    注意,当(lst=0)(无法操作)或(lst=i-1)(操作无效,因为中间没有数,不会使序列改变)时不能转移。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define X 1000000007
    using namespace std;
    int n,lst[N+5],f[N+5];
    int main()
    {
    	RI i,x;for(scanf("%d",&n),f[0]=i=1;i<=n;++i) scanf("%d",&x),//读入当前颜色
    		f[i]=f[i-1],lst[x]&&lst[x]^(i-1)&&(f[i]=(f[i]+f[lst[x]])%X),lst[x]=i;//转移,更新该颜色最后出现的位置
    	return printf("%d
    ",f[n]),0;//输出答案
    }
    

    (C):Differ by 1 Bit(点此看题面

    大致题意: 求一个(0sim2^n-1)的排列(P_i),使得(P_i)(P_{i+1})恰好有一位不同,且(P_0=A,P_{2^n-1}=B)

    首先,由于(2^n-1)是奇数,因此若(A)(B)有偶数位不同则无解,否则必然有解。

    考虑为了方便,我们只要构造一个排列(P),令(P_0=0,P_{2^n-1}=Aoplus B),并在最后给(P)中的所有数异或上(A)即可。

    而构造方式可以看作是在一个(n)维的超立方体的顶点之间走路,找到一条哈密顿路径。

    显然,我们可以先把(n-1)维的超立方体走完,然后再走到(n)维上去。

    具体实现中,每次从(Aoplus B)的二进制表示中挑选出一个(1)(假设在第(k)位),先遍历第(k)位为(0)的所有点组成的(n-1)维超立方体,然后遍历第(k)位为(1)(n-1)维超立方体。

    至于这两个(n-1)维的超立方体之间的连接点,实际上完全可以任选。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 17
    using namespace std;
    int n,lim,A,B,a[1<<N];I int C(RI x) {RI t=0;W(x) ++t,x&=x-1;return t;}
    I void Paint(CI x,CI y,CI s)//超立方体上走路
    {
    	if(C(s^(lim-1))==1) return (void)printf("%d %d ",y,x^y);RI i,j;//边界直接输出
    	for(i=0;i^n;++i) if(!((s>>i)&1)&&(x>>i&1)) for(j=0;j^n;++j) if(!((s>>j)&1)&&i^j)
    		return Paint(1<<j,y,s|(1<<i)),Paint(x^(1<<i)^(1<<j),y^(1<<i)^(1<<j),s|(1<<i));//任选
    }
    int main()
    {
    	RI i,t;if(scanf("%d%d%d",&n,&A,&B),lim=1<<n,!(C(A^B)&1)) return puts("NO"),0;//判无解
    	return puts("YES"),Paint(A^B,A,0),0;
    }
    

    (D):A Sequence of Permutations(点此看题面

    大致题意: 给定两个(1sim n)的排列(p,q),定义(f(p,q))表示第(p_i)位的值是(q_i)的一个排列。已知(a_1=p,a_2=q,a_i=f(a_{i-2},a_{i-1})(i>2)),求(a_k)

    首先给出关于置换的两种运算的定义:

    • (p imes q)((p imes q)_i=p_{q_i}),简写为(pq)。显然这一运算满足结合律但不满足交换律。
    • (p')(p_{p_i'}=i),其实(p'_{p_i}=i)也一定成立。同时还有(p imes p'=p' imes p=e),其中(e)为排列({1,2,...,n}),即单位元。

    把两种运算结合起来又有一个新的公式:((pq')'=qp')。这个公式可以扩展到多项,例如:((p'pq'qp)'=p'q'qp'p)。(相当于把式子翻转然后对于每一项改变(')的存在性)

    有了这些前置知识,就方便我们大力找规律了:(这里已经把相邻的(pp',qq')约去)

    居中形式:

    [a_1=p\a_2=q\a_3=qp'\a_4=qp'q'\a_5=qp'q'pq'\a_6=qp'q'ppq'\a_7=qp'q'ppp'qpq'\a_8=qp'q'pqp'qpq'\a_9=qp'q'pqp'p'qpq' ]

    左对齐形式:

    [egin{align}&a_1=p\&a_2=q\&a_3=qp'\&a_4=qp'q'\&a_5=qp'q'pq'\&a_6=qp'q'ppq'\&a_7=qp'q'ppp'qpq'\&a_8=qp'q'pqp'qpq'\&a_9=qp'q'pqp'p'qpq'end{align} ]

    右对齐形式:

    [egin{align}a_1=p&\a_2=q&\a_3=qp'&\a_4=qp'q'&\a_5=qp'q'pq'&\a_6=qp'q'ppq'&\a_7=qp'q'ppp'qpq'&\a_8=qp'q'pqp'qpq'&\a_9=qp'q'pqp'p'qpq'&end{align} ]

    仔细观察可以发现,令(s=qp'q'p),就有(a_{n}=s imes a_{n-6} imes s'),可以归纳证明。

    中间的一项实际上就是(a_{n\%6}),两边的(s)(s')可能有很多,但由于置换乘法满足结合律,故可快速幂高效求解。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    using namespace std;
    int n,k;struct P//置换
    {
    	int a[N+5];I P(CI x=0) {for(RI i=1;i<=x;++i) a[i]=i;}
    	I int& operator [] (CI x) {return a[x];}I int operator [] (CI x) Con {return a[x];}
     	I friend P operator ~ (Con P& A) {P B;for(RI i=1;i<=n;++i) B[A[i]]=i;return B;}//取反
    	I friend P operator * (Con P& A,Con P& B) {P C;for(RI i=1;i<=n;++i) C[i]=A[B[i]];return C;}//乘法
    }p,q,s,a[10],ans;
    I P QP(P x,RI y) {P t=P(n);W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;}//快速幂
    int main()
    {
    	RI i;for(scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&p[i]);for(i=1;i<=n;++i) scanf("%d",&q[i]);
    	a[1]=p,a[2]=q,a[3]=a[2]*(~a[1]),a[4]=a[3]*(~a[2]),a[5]=a[4]*(~a[3]),a[6]=a[5]*(~a[4]);//预处理
    	s=q*(~p)*(~q)*p,ans=QP(s,(k-1)/6)*a[(k-1)%6+1]*QP(~s,(k-1)/6);//根据规律求出最终置换
    	for(i=1;i<=n;++i) printf("%d%c",ans[i]," 
    "[i==n]);return 0;//输出答案
    }
    

    (E):Snuke the Phantom Thief(点此看题面

    大致题意: 给定二维平面上(n)个珠宝,每个珠宝有一个价值。有若干限制,规定横坐标/纵坐标小于等于/大于等于(x)的珠宝中最多选择(y)个。求能获得的最大总价值。

    一开始很(naive)地想到单纯形法,然而每个珠宝只能是选或不选((0/1)),并不可以。

    实际上这是一道网络流题。

    考虑我们枚举最终选择了(k)个珠宝,以对横坐标的限制为例:

    • L:横坐标排名大于(y)的珠宝横坐标不能小于等于(x)
    • R:横坐标排名小于等于(k-y)的珠宝纵坐标不能大于等于(y)

    于是就能求出这一维(k)个珠宝各自的坐标范围,把这个范围看作点,将其与符合该范围的珠宝连边。

    由于是两维,所以一维向珠宝入点连边,一维由珠宝出点向其连边。

    最后建来的图相当于被分为六层:超级源、横坐标范围、珠宝入点、珠宝出点、纵坐标范围、超级汇。

    然后跑最大费用最大流即可。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 80
    #define M 320
    #define LL long long
    #define INF 1e18
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    using namespace std;
    int n,m,L[N+5],R[N+5],U[N+5],D[N+5];
    struct Jewel {int x,y;LL v;}s[N+5];struct Data {char op;int x,y;}p[M+5];
    template<int PS,int ES> class MaxCostMaxlow//费用流
    {
    	private:
    		#define E(x) ((((x)-1)^1)+1)
    		#define S (4*n+1)
    		#define T (4*n+2)
    		int ee,lnk[PS+5];struct edge {int to,nxt,F;LL C;}e[2*ES+5];
    		int p[PS+5],IQ[PS+5],F[PS+5];LL C[PS+5];queue<int> q;
    		I bool SPFA()//SPFA找增广路
    		{
    			RI i,k;for(i=0;i<=4*n+2;++i) F[i]=1e9,C[i]=-INF;q.push(S),C[S]=0;
    			W(!q.empty()) for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt)
    			{
    				if(!e[i].F||C[k]+e[i].C<=C[e[i].to]) continue;
    				C[e[i].to]=C[k]+e[p[e[i].to]=i].C,F[e[i].to]=min(F[k],e[i].F),
    				!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1);
    			}return F[T]!=1e9;
    		}
    	public:
    		I void Clear() {ee=0,memset(lnk,0,sizeof(lnk));}//清空
    		I void Add(CI x,CI y,CI f,Con LL& c)//建边
    		{
    			e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c,
    			e[++ee].nxt=lnk[y],e[lnk[y]=ee].to=x,e[ee].F=0,e[ee].C=-c;
    		}
    		I LL MCMF(RI k)
    		{
    			RI x;LL t=0;W(SPFA())
    			{
    				k-=F[T],t+=C[T]*F[T],x=T;
    				W(x^S) e[p[x]].F-=F[T],e[E(p[x])].F+=F[T],x=e[E(p[x])].to;
    			}return k?0:t;//如果流量与规定选择数量不同返回0
    		}
    };MaxCostMaxlow<4*N+2,3*N+2*N*N> F;
    int main()
    {
    	RI i,j,k;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%lld",&s[i].x,&s[i].y,&s[i].v);
    	for(scanf("%d",&m),i=1;i<=m;++i) cin>>p[i].op,scanf("%d%d",&p[i].x,&p[i].y);
    	LL ans=0;for(i=1;i<=n;++i)//枚举最终选的珠宝数
    	{
    		for(j=1;j<=i;++j) L[j]=D[j]=0,R[j]=U[j]=100;for(j=1;j<=m;++j) switch(p[j].op)//枚举限制确定范围
    		{
    			case 'L':p[j].y<i&&Gmax(L[p[j].y+1],p[j].x+1);break;
    			case 'R':p[j].y<i&&Gmin(R[i-p[j].y],p[j].x-1);break;
    			case 'D':p[j].y<i&&Gmax(D[p[j].y+1],p[j].x+1);break;
    			case 'U':p[j].y<i&&Gmin(U[i-p[j].y],p[j].x-1);break;
    		}
    		//1~n珠宝入点,n+1~2n珠宝出点,2n+1~3n横坐标范围,3n+1~4n纵坐标范围,4n+1/2超级源/汇
    		for(j=2;j<=i;++j) Gmax(L[j],L[j-1]),Gmax(D[j],D[j-1]);
    		for(j=i-1;j;--j) Gmin(R[j],R[j+1]),Gmin(U[j],U[j+1]);F.Clear();
    		for(j=1;j<=n;++j) F.Add(j,n+j,1,s[j].v);for(j=1;j<=i;++j) F.Add(S,2*n+j,1,0),F.Add(3*n+j,T,1,0);//入点与出点;超级源/汇与坐标范围
    		for(j=1;j<=i;++j) for(k=1;k<=n;++k) L[j]<=s[k].x&&s[k].x<=R[j]&&(F.Add(2*n+j,k,1,0),0);//横坐标范围与入点
    		for(j=1;j<=i;++j) for(k=1;k<=n;++k) D[j]<=s[k].y&&s[k].y<=U[j]&&(F.Add(n+k,3*n+j,1,0),0);//出点与纵坐标范围
    		ans=max(ans,F.MCMF(i));//统计最优答案
    	}return printf("%lld
    ",ans),0;
    }
    
  • 相关阅读:
    Mybatis plus 配置
    logback配置
    iview-ui-project-4.0 安装与配置
    Linux系统下Redis安装与配置
    Java中枚举的用法
    Mysql 查询所有课程的成绩第2名到第3名的学生信息及该课程成绩
    java 基础知识一 初识java
    docker 查看 挂载目录
    sqlserver统计所有表及表中记录数
    centos7配置禁用ipv6
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC031.html
Copyright © 2020-2023  润新知