AtCoder Grand Contest 031
Preface
这场后面题目好难啊,C就开始思博了
A - Colorful Subsequence
考虑DP,\(f_i\)表示前\(i\)个数的答案,考虑如何去除重复的限制
对于当前的\(i\),设之前\(s_j=s_i\)的\(j\)有\(c\)个,显然我们在这\(c+1\)个数里只能选出一个来,因此转移\(f_i+=\frac{f_{i-1}}{c+1}\)
然后我们发现数组都不用开,直接\(O(n)\)做即可
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+7;
int n,c[N],ans; char s[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
RI i,j; for (scanf("%d%s",&n,s+1),ans=i=1;i<=n;++i)
++c[s[i]-'a'],(ans+=1LL*ans*quick_pow(c[s[i]-'a'])%mod)%=mod;
return printf("%d",ans-1),0;
}
B - Reversi
设\(f_i\)表示前\(i\)个石子染色的方案数,很容易发现我们对于某种颜色,我们只需要找出这个颜色的前驱
考虑给这段区间染色,显然染过之后这段区间内的数都不会再染色了,因此我们可以直接从前驱转移来
但这样可能会无法从前驱的前驱转移来,因此我们每次把同种颜色的贡献一起累加即可
注意特判前驱不存在以及相同的颜色相邻的情况,复杂度\(O(n)\)
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
int n,x,pre[N],f[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
int main()
{
RI i,j; for (scanf("%d",&n),f[0]=i=1;i<=n;pre[x]=i++)
if (scanf("%d",&x),f[i]=f[i-1],pre[x]&&pre[x]!=i-1) inc(f[i],f[pre[x]]);
return printf("%d",f[n]),0;
}
C - Differ by 1 Bit
我们可以把所有数都\(\operatorname{xor}\)上\(A\),这样就是找一条\(0\to A\operatorname{xor} B\)的路径,最后再\(\operatorname{xor}\)回去即可
首先考虑判断无解,我们发现每次操作会改变一位的值,换句话说会改变\(1\)的个数的奇偶性
然后我们一共要做奇数次变换,因此若\(A\operatorname{xor} B\)的\(1\)的个数是奇数,那么才合法
接下来考虑如何构造,我们考虑把问题抽象成在一个\(n\)维的超立方体的顶点之间走路,让你找出一条路径能走到目标点并且需要经过所有顶点
很容易想到我们可以先把\(n-1\)维的超立方体走完,然后一步走到\(n\)维的上面去
具体地,我们每次选出一个二进制位\(p\)满足\(A\operatorname{xor} B\)在第\(p\)位上的值是\(1\)
我们先遍历第\(p\)位为\(0\)的所有点组成的\(n-1\)维超立方体后,在去遍历第\(p\)位为\(1\)的所有点组成的\(n-1\)维超立方体
显然在两个\(n-1\)维的超立方体间的连接点是任意取的,因此我们前面的判断是正确的
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=17;
int n,a,b,lim;
inline int count(CI x,int ret=0)
{
for (RI i=0;i<n;++i) ret+=((x>>i)&1); return ret;
}
inline void DFS(CI x,CI y,CI cs)
{
if (count(cs)==1) return (void)(printf("%d %d ",y,x^y));
for (RI i=0,j;i<n;++i) if (((x>>i)&1)&&((cs>>i)&1))
for (j=0;j<n;++j) if (((cs>>j)&1)&&i!=j)
return (void)(DFS(1<<j,y,cs^(1<<i)),DFS(x^(1<<i)^(1<<j),y^(1<<i)^(1<<j),cs^(1<<i)));
}
int main()
{
scanf("%d%d%d",&n,&a,&b); if (count(a^b)&1)
puts("YES"),DFS(a^b,a,(1<<n)-1); else puts("NO"); return 0;
}
D - A Sequence of Permutations
大力找规律题。我们首先发现关于\(f(p,q)\),设其为\(c\),则:
考虑我们记\(c_i=q_{p'_i}\)为\(c=qp'\),即我们定义其为关于置换群的一种运算
经过实践检验我们发现它满足结合律,但不满足交换律
同时我们发现\((ab')'=ab',(ab'c)'=c'ba'\),因此接下来我们来大力推导找规律:
由于相邻的\(pp'\)以及\(qq'\)可以约去,因此我们改写一下:
很容易发现若我们令\(T=qp'q'p\),则对于\(n>6\),均有\(a_n=Ta_{n-6}T'\)
证明可以用归纳法,这里略去我才不会说我是找规律出来的
我们发现之前定义的运算和矩阵乘法有着相同的性质,因此我们可以用快速幂处理
总复杂度\(O(n\log k)\)
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k;
struct permutation
{
int a[N];
inline permutation(void) { memset(a,0,sizeof(a)); }
inline int& operator [] (CI x) { return a[x]; }
inline friend permutation operator ~ (permutation A)
{
permutation tp; for (RI i=1;i<=n;++i) tp[A[i]]=i; return tp;
}
inline friend permutation operator * (permutation A,permutation B)
{
permutation C; for (RI i=1;i<=n;++i) C[i]=A[B[i]]; return C;
}
inline friend permutation operator ^ (permutation A,int p)
{
permutation t; for (RI i=1;i<=n;++i) t[i]=i;
for (;p;p>>=1,A=A*A) if (p&1) t=t*A; return t;
}
inline void print(void)
{
for (RI i=1;i<=n;++i) printf("%d ",a[i]);
}
}a[10],T;
int main()
{
RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[1][i]);
for (i=1;i<=n;++i) scanf("%d",&a[2][i]);
for (i=3;i<=6;++i) a[i]=a[i-1]*(~a[i-2]);
int d=k/6,r=k%6; if (!r) --d,r=6; T=a[2]*(~a[1])*(~a[2])*a[1];
return ((T^d)*a[r]*((~T)^d)).print(),0;
}
E - Snuke the Phantom Thief
很妙的网络流建模题,增长了有用的姿势
首先我们考虑一维的问题怎么做,考虑对限制\((a_i,b_i)\)进行转化
一个比较显然的的结论,若限制\(\le a_i\)的珠宝有\(b_i\)个,则选出的珠宝中第\(b_i+1\)大的一定\(>a_i\)
然后考虑\(\le a_i\)如何转化,直接做不方便,我们考虑枚举选出的珠宝总数\(k\)
那么若限制\(\ge a_i\)的珠宝有\(b_i\)个,则选出的珠宝中第\(k-b_i\)大的一定\(<a_i\)
那么现在我们可以得到\(k\)个二元组\((l_i,r_i)\),表示选出的第\(i\)个珠宝的坐标范围,显然\(l_i,r_i\)均要满足单调性
现在问题其实就是每个二元组可以在它能选择的珠宝中选择一个,每个二元组只能和一个珠宝匹配,很显然就是个最大权匹配问题
那么现在两维的也很简单,我们把\(n\)个点拆成两份,分别表示横纵坐标,与对应的关于横纵坐标的\(k\)个点连边,同时\(n\)个点连\((i,i')\)的边表示这个珠宝只能选一次
直接跑最大费用最大流即可,总复杂度\(费用流O(n\times \text{费用流})\)
#include<cstdio>
#include<cctype>
#include<iostream>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
const int N=350,M=(N*N<<1)+(3*N);
const long long INF=1e18;
struct edge
{
int to,nxt,v; long long c;
}e[M<<1]; char opt[N]; long long ans,v[N];
int n,m,head[N],cnt,x[N],y[N],L[N],R[N],U[N],D[N],a[N],b[N],s,t;
inline char getch(void)
{
char ch; while (!isalpha(ch=getchar())); return ch;
}
inline void addedge(CI x,CI y,CI v,const long long& c)
{
e[++cnt]=(edge){y,head[x],v,c}; head[x]=cnt;
e[++cnt]=(edge){x,head[y],0,-c}; head[y]=cnt;
}
#define to e[i].to
namespace NF //Network_Flow
{
queue <int> q; int pre[N],lst[N],cap[N]; long long dis[N]; bool vis[N];
inline bool SPFA(CI s,CI t)
{
RI i; for (i=s;i<=t;++i) dis[i]=-INF,cap[i]=1e9,pre[i]=-1;
pre[s]=0; q.push(s); dis[s]=0; vis[s]=1; while (!q.empty())
{
int now=q.front(); q.pop(); vis[now]=0;
for (i=head[now];i;i=e[i].nxt)
if (e[i].v&&dis[to]<dis[now]+e[i].c)
{
dis[to]=dis[now]+e[i].c; pre[to]=now; lst[to]=i;
cap[to]=min(cap[now],e[i].v); if (!vis[to]) vis[to]=1,q.push(to);
}
}
return ~pre[t];
}
inline long long MCMF(CI s,CI t)
{
long long ret=0; while (SPFA(s,t))
{
ret+=dis[t]*cap[t]; for (int nw=t;nw;nw=pre[nw])
e[lst[nw]].v-=cap[t],e[lst[nw]^1].v+=cap[t];
}
return ret;
}
inline void clear(CI n)
{
cnt=1; for (RI i=0;i<=n;++i) head[i]=0;
}
};
#undef to
inline long long calc(CI k)
{
RI i,j; for (i=1;i<=k;++i) L[i]=D[i]=0,R[i]=U[i]=1e9;
for (NF::clear(2*n+2*k+1),i=1;i<=m;++i) if (b[i]<k)
{
if (opt[i]=='L') L[b[i]+1]=a[i]+1;
if (opt[i]=='R') R[k-b[i]]=a[i]-1;
if (opt[i]=='D') D[b[i]+1]=a[i]+1;
if (opt[i]=='U') U[k-b[i]]=a[i]-1;
}
for (i=2;i<=k;++i) L[i]=max(L[i],L[i-1]),D[i]=max(D[i],D[i-1]);
for (i=k-1;i;--i) R[i]=min(R[i],R[i+1]),U[i]=min(U[i],U[i+1]);
for (s=0,t=2*n+2*k+1,i=1;i<=k;++i) addedge(s,i,1,0),addedge(k+2*n+i,t,1,0);
for (i=1;i<=n;++i) addedge(k+i,k+n+i,1,v[i]);
for (i=1;i<=k;++i) for (j=1;j<=n;++j)
{
if (L[i]<=x[j]&&x[j]<=R[i]) addedge(i,k+j,1,0);
if (D[i]<=y[j]&&y[j]<=U[i]) addedge(k+n+j,k+2*n+i,1,0);
}
return NF::MCMF(s,t);
}
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%lld",&x[i],&y[i],&v[i]);
for (scanf("%d",&m),i=1;i<=m;++i) opt[i]=getch(),scanf("%d%d",&a[i],&b[i]);
for (i=1;i<=n;++i) ans=max(ans,calc(i)); return printf("%lld",ans),0;
}
Postscript
暑假要结束了,赶紧冲冲冲一波!