(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),转移方程显然:
注意,当(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')约去)
居中形式:
左对齐形式:
右对齐形式:
仔细观察可以发现,令(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;
}