星际广播
如果已知最终变成了字母 \(x\)
设 \(f_{i,j}\) 表示前 \(i\) 个字符用 \(j\) 次区间操作的局面下最少使用多少次单点操作,简单的转移是 \(f_{i,j}\leftarrow \min\left\{ f_{i-1,j}+[s_{i+1}=x],f_{i-l,j-1}\right\}\)
观察一些贪心策略可以发现在能进行区间操作的次数变多时,每次能造成单点操作的减少量 是单调不升的,也就是一个凸函数
因为字符集大小只有 \(3\),所以对于全变成 R,Y,B
都做一次 \(\rm WQS\) 二分即可
Code Display
const int N=1e6+10;
int n,m,l;
double dp[N];
int num[N];
char s[N];
inline void DP(double cst,char tar){
for(int i=1;i<=n;++i){
dp[i]=dp[i-1]+(s[i]!=tar);
num[i]=num[i-1];
if(i>=l&&dp[i-l]+cst<dp[i]) dp[i]=dp[i-l]+cst,num[i]=num[i-l]+1;
}
return ;
}
inline int calc(char tar){
double ans=1e9+1,l=-1e9,r=1e9;
while(r-l>1e-6){
double mid=(l+r)/2;
DP(mid,tar);
if(num[n]>=m) l=mid,ans=mid;
else r=mid;
}
DP(ans,tar);
return floor(0.3+dp[n]-m*ans);
}
signed main(){
freopen("radio.in","r",stdin); freopen("radio.out","w",stdout);
n=read(); m=read(); l=read();
scanf("%s",s+1);
print(min(calc('R'),min(calc('Y'),calc('B'))));
return 0;
}
星际航道
原图是个网格图,对于原图上任意一棵生成树,由于没有形成环,那么一定没有封闭区域,也就是说将边集补集在对偶图上表示出来,一定也是一个树
所以可以得到原图最小生成树和对偶图上最大生成树是交集为空的全部边集的一个划分
使用两棵 \(\rm LCT\) 同时维护原图的最小生成树和对偶图的最大生成树
每次修改先找到这条边在哪棵生成树上,并且尝试在另一棵生成树上插入之,如果成功加入了则将删掉的边加回这棵树即可
Code Display
const int N=3e5+10;
int n,m,Q,nds,eid,lans;
inline void decode(char ch, int &x, int &y, int &w) {
// R is 'r'
// C is 'c'
static int mask = 0xfffff;
w = (int) ((w ^ lans) & mask);
if(ch == '-') x = (x + lans - 1) % n + 1, y = (y + lans - 1) % (m- 1) + 1;
if(ch == '|') x = (x + lans - 1) % (n - 1) + 1, y = (y + lans - 1) % m + 1;
}
struct LCT{
int u[N],v[N],nds;
int ls[N],rs[N],fa[N],tag[N];
int mx[N],val[N],mxpos[N];
inline void push_up(int x){
mx[x]=val[x]; mxpos[x]=x;
if(ls[x]&&mx[ls[x]]>mx[x]) mx[x]=mx[ls[x]],mxpos[x]=mxpos[ls[x]];
if(rs[x]&&mx[rs[x]]>mx[x]) mx[x]=mx[rs[x]],mxpos[x]=mxpos[rs[x]];
return ;
}
inline void push_rev(int x){swap(ls[x],rs[x]); tag[x]^=1; return ;}
inline bool isroot(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}
inline void push_down(int x){
if(tag[x]){
if(ls[x]) push_rev(ls[x]);
if(rs[x]) push_rev(rs[x]);
tag[x]=0;
}
return ;
}
inline void rotate(int x){
int y=fa[x],z=fa[y];
if(!isroot(y)) if(ls[z]==y) ls[z]=x; else rs[z]=x;
if(ls[y]==x) ls[y]=rs[x],fa[rs[x]]=y,rs[x]=y;
else rs[y]=ls[x],fa[ls[x]]=y,ls[x]=y;
fa[y]=x; fa[x]=z;
return push_up(y),push_up(x);
}
int stk[N],top;
inline void splay(int x){
int y=x; stk[top=1]=y;
while(!isroot(y)) stk[++top]=y=fa[y];
while(top) push_down(stk[top--]);
while(!isroot(x)){
int y=fa[x],z=fa[y];
if(!isroot(y)) rotate((ls[z]==y)^(ls[y]==x)?x:y);
rotate(x);
} return ;
}
inline void access(int x){
for(int y=0;x;x=fa[y=x]){
splay(x);
rs[x]=y;
push_up(x);
}
return ;
}
inline void make_root(int x){
access(x),splay(x);
return push_rev(x);
}
inline void split(int x,int y){make_root(x); access(y),splay(y); return ;}
inline int findroot(int x){
access(x); splay(x);
while(ls[x]) push_down(x),x=ls[x];
return splay(x),x;
}
inline void link(int x,int y){
make_root(x); access(y); splay(y);
fa[x]=y;
return ;
}
inline void cut(int x,int y){
split(x,y);
fa[x]=ls[y]=0;
return push_up(y);
}
int sum;
bool exi[N];
inline void fetch(int id){
sum+=val[id+nds];
push_up(id+nds);
link(id+nds,u[id]);
link(id+nds,v[id]);
exi[id]=1;
}
inline void lost(int id){
cut(id+nds,u[id]);
cut(id+nds,v[id]);
sum-=val[id+nds];
exi[id]=0;
return ;
}
inline int insert_edge(int id){
if(findroot(u[id])!=findroot(v[id])){
fetch(id);
return -1;
}
split(u[id],v[id]);
int pos=mxpos[v[id]];
if(mx[v[id]]<=val[id+nds]) return 0;
lost(pos-nds);
fetch(id);
return pos-nds;
}
inline void upd_edge(int id,int v){
splay(id+nds);
if(exi[id]) sum+=v-val[id+nds];
val[id+nds]=v;
push_up(id+nds);
}
inline void init(int num){
nds=num;
rep(i,1,nds) val[i]=-1e18,push_up(i);
}
}A,B;
// A : original graph
// B : dual graph
signed main(){
freopen("channel.in","r",stdin); freopen("channel.out","w",stdout);
int typ=read();
n=read(); m=read(); Q=read();
vector<vector<int> >id(n+1,vector<int>(m+1));
vector<vector<int> >lef(n+1,vector<int>(m)),down(n,vector<int>(m+1));
nds=0;
rep(i,1,n) rep(j,1,m) id[i][j]=++nds;
int Outside=nds+1;
A.init(nds); B.init(Outside);
rep(i,1,n) rep(j,1,m){
if(i<n){
down[i][j]=++eid;
A.u[eid]=id[i][j]; A.v[eid]=id[i+1][j];
B.u[eid]=j==m?Outside:id[i][j]; B.v[eid]=j==1?Outside:id[i][j-1];
if(~A.insert_edge(eid)) B.insert_edge(eid);
}
if(j<m){
lef[i][j]=++eid;
A.u[eid]=id[i][j]; A.v[eid]=id[i][j+1];
B.u[eid]=i==1?Outside:id[i-1][j]; B.v[eid]=i==n?Outside:id[i][j];
if(~A.insert_edge(eid)) B.insert_edge(eid);
}
}
while(Q--){
char dir=getchar();
while(dir!='|'&&dir!='-') dir=getchar();
int x=read(),y=read(),v=read();
if(typ) decode(dir,x,y,v);
//from to dis id flag
int cur=0,edg=-1;
if(dir=='|') cur=down[x][y];
else cur=lef[x][y];
A.upd_edge(cur,v);
B.upd_edge(cur,-v);
if(A.exi[cur]){
if((edg=B.insert_edge(cur))){
A.lost(cur);
A.fetch(edg);
}
}else{
if((edg=A.insert_edge(cur))){
B.lost(cur);
B.fetch(edg);
}
}
print(lans=A.sum);
}
return 0;
}
星际联邦
内向森林计数可以通过加虚点来套矩阵树定理,把虚根的一行一列去掉得到的主子式做行列式即可
本题中这样的主子式是一个循环矩阵(对角线上元素是 \(\sum w_i+1\),而 \(i\neq j\) 的 \(A_{i,j}=-w_{(i-j+n)\bmod \ n}\))
设 \(f(x)=\sum w_i+1+\sum\limits^{n-1}_{k=1} (-w_{n-k})x^k\)
解决循环矩阵行列式求值的方法就是让其左乘一个范德蒙德矩阵,取单位根向量 \(Vec_i=\omega_{n}^i\) 并构造对应的范德蒙德矩阵
手玩出来 \(AB\) 的形式可以发现是 \(B\) 矩阵每行乘 \(f(\omega_{n}^i)\) 来得到,那么根据 \(|AB|=|A||B|\) \(|A|\) 就是 \(\prod_{i=0}^{n-1} f(\omega_{n}^i)\)
使用 \(\rm NTT\) 即可
upd:没想到,居然是 CTT2020 原题
Code Display
const int N=1<<20;
int r[N],W[N],n;
inline void NTT(vector<int> &f,int lim,int opt){
f.resize(lim);
for(int i=0;i<lim;++i){
r[i]=r[i>>1]>>1|((i&1)?(lim>>1):0);
if(i<r[i]) swap(f[i],f[r[i]]);
}
for(int p=2;p<=lim;p<<=1){
int len=p>>1; W[0]=1; W[1]=ksm(3,(mod-1)/p);
if(opt==-1) W[1]=ksm(W[1],mod-2);
for(int j=2;j<len;++j) W[j]=mul(W[j-1],W[1]);
for(int k=0;k<lim;k+=p){
for(int l=k;l<k+len;++l){
int tt=mul(f[l+len],W[l-k]);
f[l+len]=del(f[l],tt);
ckadd(f[l],tt);
}
}
}
if(opt==-1) for(int i=0,tmp=ksm(lim,mod-2);i<lim;++i) ckmul(f[i],tmp);
return ;
}
signed main(){
freopen("federation.in","r",stdin); freopen("federation.out","w",stdout);
int k=read();
vector<int> f(n=1<<k);
f[0]=1;
for(int i=n-1;i>=1;--i) ckadd(f[0],f[i]=read()),f[i]=del(0,f[i]);
NTT(f,n,1);
int ans=1;
for(int i=0;i<n;++i) ckmul(ans,f[i]);
print(ans);
return 0;
}