【考试总结】2022-03-06
高维游走
首先使用 库默尔定理 发现将 \(t_0\) 划分成 \(\{a_0\dots a_m\},\forall i\neq j,a_i\cap a_j=\emptyset,\forall i,a_i\subseteq t_0\cap t_i\) 才能在组合数中构成奇数
最朴素的想法是直接 std::bitset
来对每个 \(f(i)\) 计算奇偶性,但是可以通过划分阶段来将问题简化
逐二进制位转移是个不错的选择,设 \(g_{i,x}\) 表示已经转移了前 \(0\sim i\) 位,\(f(x)\bmod 2\) 的值,转移枚举第 \(i+1\) 位这个 \(1\) 被哪个 \(a_j\) 选走了,就可以让 \(g_{i+1,j\times2^{i+1}+x}\) 产生更新
这时如果将 \(x\bmod 2^{i+1}\) 分组,那么 \(x+j\times 2^{i+1}\equiv x\mod 2^{i+1}\),也就是说奇偶性的变化只会在组内发生
那么由于已经转移的权值不超过 \(2^i\) 每组非零的 \(x\) 的数量不会超过 \(m\times 2^i\),即每个组里面的元素不超过 \(m\) 个,又根据权值是 \(0/1\),可以使用二进制位压缩
本质上 \(0/1\) 表示不相同的组是 \(\Theta(2^m)\) 数量级的,所以每个余数都开个变量非常蠢,再开桶 \(ton_S\) 表示压缩后是 \(S\) 的组的数量
对于 \(t_0\) 不包含的位,从 \(S\bmod\ 2_i\to x \bmod\ 2^{i+1}\) 发生的变化就是商是奇数的和商是偶数的分开了,这样子的变化可以预处理
对于 \(t_0\) 包含的位需要先枚举 同样包含这位的 \(t_i\) 把方案数变成偶数的异或掉再转移即可
Code Display
const int N=2000010;
int t[N],n,m,dp[2][N],ev[N],od[N];
signed main(){
freopen("travel.in","r",stdin); freopen("travel.out","w",stdout);
for(int i=1;i<=N-10;++i){
ev[i]=(ev[i>>2]<<1)|((i>>1)&1);
od[i]=(od[i>>2]<<1)|(i&1);
}
int T=read(); while(T--){
n=read(); rep(i,0,n) t[i]=read();
int cur=0,S=(1<<(n+1)); --S;
dp[cur][1]=1;
for(int i=0;i<31;++i){
if(t[0]>>i&1){
for(int st=0;st<=S;++st) if(dp[cur][st]){
int nxt=st;
for(int j=1;j<=n;++j) if(t[j]>>i&1) nxt^=(st<<j);
dp[cur^1][ev[nxt]]+=dp[cur][st];
dp[cur^1][od[nxt]]+=dp[cur][st];
dp[cur][st]=0;
}
}else{
for(int st=0;st<=S;++st) if(dp[cur][st]){
dp[cur^1][ev[st]]+=dp[cur][st];
dp[cur^1][od[st]]+=dp[cur][st];
dp[cur][st]=0;
}
}
cur^=1;
}
int ans=0;
for(int i=1;i<=S;++i) ans+=dp[cur][i]*__builtin_popcount(i),dp[cur][i]=0;
print(ans);
}
return 0;
}
过山车
如果只判定合法性的做法就是将每个点拆成横向/竖向两个点并再拉一个本原点出来
黑白染色之后本原点向源点/汇点连流量为 \(2\) 的边,同时向横向、竖向两个点都连流量为 \(2\) 的边
根据网格图将各个点的横向/纵向点连起来看看最大流是不是点数即可
如果附加权值考虑 “减少竖直边” 这样的求解方式,从本原点连向横向/纵向点流量为 \(2\) 边中一个带 \(0\) 权,另一个带 \(a_{i,j}\) 的权值即可
由于是最小费用最大流,那么一定先选择两个 \(0\) 权边,也就是拐弯的方法
Code Display
const int N=1e5+10,inf=0x3f3f3f3f;
struct edge{int to,nxt,lim,cst;}e[N<<2];
int head[N],dst[N],pre[N],incf[N];
int tot,id1[200][200],id[200][200],id2[200][200],S,T;
int n,m,cnt=1,ban[200][200],val[200][200];
bool inq[N];
inline bool spfa(){
for(int i=1;i<=tot;++i) incf[i]=0,dst[i]=inf; dst[S]=0; incf[S]=inf;
queue<int> q; q.push(S);
while(q.size()){
int fr=q.front(); q.pop(); inq[fr]=0;
for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){
int t=e[i].to; if(dst[fr]+e[i].cst<dst[t]){
dst[t]=dst[fr]+e[i].cst; incf[t]=min(incf[fr],e[i].lim); pre[t]=i;
if(!inq[t]) inq[t]=1,q.push(t);
}
}
}
return dst[T]!=inf;
}
inline void adde(int u,int v,int w,int c){
e[++cnt]={v,head[u],w,c}; head[u]=cnt;
return ;
}
inline void add(int u,int v,int w,int c){return adde(u,v,w,c),adde(v,u,0,-c);}
signed main(){
freopen("roller.in","r",stdin); freopen("roller.out","w",stdout);
n=read(); m=read();
int sum=0,block=0;
S=++tot; T=++tot;
rep(i,1,n) rep(j,1,m) ban[i][j]=read();
rep(i,1,n) rep(j,1,m){
val[i][j]=read();
sum+=(!ban[i][j])*val[i][j];
block+=!ban[i][j];
if(!ban[i][j]){
id1[i][j]=++tot,id2[i][j]=++tot,id[i][j]=++tot;
if((i+j)&1){
add(S,id[i][j],2,0);
add(id[i][j],id1[i][j],1,0);
add(id[i][j],id1[i][j],1,val[i][j]);
add(id[i][j],id2[i][j],1,0);
add(id[i][j],id2[i][j],1,val[i][j]);
}else{
add(id[i][j],T,2,0);
add(id1[i][j],id[i][j],1,0);
add(id1[i][j],id[i][j],1,val[i][j]);
add(id2[i][j],id[i][j],1,0);
add(id2[i][j],id[i][j],1,val[i][j]);
}
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j) if(!ban[i][j]&&(i+j)%2){
if(i+1<=n&&!ban[i+1][j]) add(id1[i][j],id1[i+1][j],1,0);
if(i-1&&!ban[i-1][j]) add(id1[i][j],id1[i-1][j],1,0);
if(j+1<=m&&!ban[i][j+1]) add(id2[i][j],id2[i][j+1],1,0);
if(j-1&&!ban[i][j-1]) add(id2[i][j],id2[i][j-1],1,0);
}
}
int flow=0;
while(spfa()){
int x=T;
while(x!=S){
e[pre[x]].lim-=incf[T];
e[pre[x]^1].lim+=incf[T];
x=e[pre[x]^1].to;
}
flow+=incf[T]; sum-=incf[T]*dst[T];
}
if(flow==block) print(sum);
else print(-1);
return 0;
}
木棍
将线段转化成左端点(也就是让限制的右端点 \(-k\)),同时记 \(s_i\) 表示前 \(i\) 个整点上左端点数量
那么线段长度是 \(k\Leftrightarrow\) \(\forall l,r,s_r-s_{l}\le \lceil\frac{r-l}k\rceil\)
对于 \(m\) 条限制中也就是 \(s_{x-1}\ge s_y-c\);\(n\) 个木棍的位置可以表达成 \(s_{b_i}\ge s_{a-1}+1\),最后根据实际含义还还有 \(s_{i}\ge s_{i+1}-1\)
先考察 \(s_{b}\ge s_a+1\),设 \(U(l,r)\) 表示区间 \([l,r]\) 里面的线段数量,使用 \(\rm Hall\) 定理发现 \((a,b)\) 的限制需要表示为 \(s_r-s_{l-1}\ge U(l,r)\)
\(n\) 较大时这样子的连边方式接受不了,尝试只保留 “限制区间” 的左端点\(-1\) 和右端点作为 关键点 及关键点所涉及到的边(也就是导出子图)来进行差分约束
对于原图上的一个不全是关键点的正环,找到一个非关键点,它在环上的连边不能是 \(m\) 条限制中的边,如果有连续的 \(\ge U(l,r)\) 或者 \(\le -\lceil\frac {len}k\rceil\) 边,合并起来并不会让环的权值减小
那么保持 \(U(l,r)\) 不变,让这个点所对应的区间缩小一定会让环上的权值不降,所以可以只对导出子图做差分约束
在进行差分约束的过程中,观察边的构成,容易使用 \(\Theta(n+m)\) 来处理 \(s_i\le s_{i+1},s_{x-1}\ge s_y-c\)
对于 \(s_r-s_{l-1}\ge U(l,r)\) 这是经典的扫描线问题,在我的 chkmax
式差分约束中可以表示为 \(s_r\ge \max\limits_{l<r}\{s_{l-1}+U(l,r)\}\)
第四种 \(s_r-s_{l}\le \lceil\frac{r-l}k\rceil\Rightarrow s_l\ge s_r-\lceil\frac rk\rceil+\lceil\frac lk\rceil-(r\bmod \ k>l\bmod \ k)\) 可以将 \(i\in[1,n]\) 按照模 \(k\) 的余数排序之后,使用 \(\text {Fenwick Tree}\) 维护后缀最大值即可
注意更新是有顺序的,在正序扫描的时候对于同余数的先都插入再查询,而倒序时要先都查询再插入,来避免漏转移或者错误的转移
如果把线段树换成 zkw 线段树
之后感觉没有常数优化余地了,不知道 \(\text{std}\) 是怎么做的,只能把正环长度卡到 \(3000\) 了
Code Display
const int N=4010,inf=0x3f3f3f3f;
int n,m,k,x[N],y[N],c[N],a[N],b[N];
int num,ar[N];
vector<int> inter[N];
int now[N],nxt[N];
struct Fenwick{
int c[N];
inline void clear(){memset(c,-0x3f,sizeof(c));}
inline void insert(int x,int v){
for(;x;x-=x&(-x)) ckmax(c[x],v);
return ;
}
inline int query(int x){
int Mx=0;
for(;x<=num;x+=x&(-x)) ckmax(Mx,c[x]);
return Mx;
}
}T;
struct Seg{
#define ls p<<1
#define rs p<<1|1
int Mx[N<<2],tag[N<<2],bit;
inline void push_up(int p){Mx[p]=max(Mx[ls],Mx[rs])+tag[p];}
inline void build(int n){
bit=1; while(bit<=n+1) bit<<=1;
for(int i=bit+1;i<=bit+n;++i) Mx[i]=now[i-bit],tag[i]=0;
for(int i=bit-1;i>=1;--i) Mx[i]=max(Mx[i<<1],Mx[i<<1|1]),tag[i]=0;
return ;
}
inline void upd(int st,int ed){
st+=bit-1; ed+=bit+1;
while(st!=ed-1){
if(!(st&1)) Mx[st^1]++,tag[st^1]++;
if(ed&1) Mx[ed^1]++,tag[ed^1]++;
push_up(st>>=1); push_up(ed>>=1);
}
while(st>>1) push_up(st>>=1);
return ;
}
inline int query(int st,int ed){
st+=bit-1; ed+=bit+1;
int lef=0,rig=0;
while(st!=ed-1){
if(!(st&1)) ckmax(lef,Mx[st^1]);
if(ed&1) ckmax(rig,Mx[ed^1]);
lef+=tag[st>>=1]; rig+=tag[ed>>=1];
}
int Mx=max(lef,rig);
while(st>>1) Mx+=tag[st>>=1];
return Mx;
}
#undef ls
#undef rs
}seg;
int quo[N],rem[N],id[N];
signed main(){
freopen("stick.in","r",stdin); freopen("stick.out","w",stdout);
n=read(); m=read(); k=read();
rep(i,1,n){
a[i]=read(),b[i]=read()-k;
ar[++num]=a[i]-1; ar[++num]=b[i];
}
rep(i,1,m){
x[i]=read(),y[i]=read()-k,c[i]=read();
ar[++num]=x[i]-1; ar[++num]=y[i];
}
sort(ar+1,ar+num+1); num=unique(ar+1,ar+num+1)-ar-1;
rep(i,1,num){
quo[i]=ar[i]/k,rem[i]=(ar[i]%k+k)%k,id[i]=i;
while(k*quo[i]>ar[i]) quo[i]--;
}
sort(id+1,id+num+1,[&](const int &x,const int &y){return rem[x]<rem[y];});
rep(i,1,n){
a[i]=lower_bound(ar+1,ar+num+1,a[i]-1)-ar;
b[i]=lower_bound(ar+1,ar+num+1,b[i])-ar;
inter[b[i]].push_back(a[i]);
}
rep(i,1,m){
x[i]=lower_bound(ar+1,ar+num+1,x[i]-1)-ar;
y[i]=lower_bound(ar+1,ar+num+1,y[i])-ar;
}
//four kinds of edges
// first -> s[x[i]-1]>=s[y[i]]+c;
// second-> s[i]<=s[i+1]
// third -> s[i]>=s[j]-(i-j+k-1)/k
// fourth-> scaning line
memset(now,-0x3f,sizeof(now)); now[0]=0;
memset(nxt,-0x3f,sizeof(nxt));
for(int turn=1;turn<=num;++turn){
nxt[0]=now[0];
for(int i=1;i<=num;++i) nxt[i]=max(nxt[i-1],now[i]);
//second type
for(int i=1;i<=m;++i) ckmax(nxt[x[i]],now[y[i]]-c[i]);
//first type
seg.build(num);
for(int i=1;i<=num;++i){
for(auto t:inter[i]) seg.upd(1,t);
if(i-1) ckmax(nxt[i],seg.query(1,i-1));
}
//scaning line
T.clear();
for(int i=1;i<=num;++i){
int rig=i;
while(rig<num&&rem[id[rig+1]]==rem[id[i]]) ++rig;
for(int j=i;j<=rig;++j) T.insert(id[j],now[id[j]]-quo[id[j]]);
for(int j=i;j<=rig;++j) ckmax(nxt[id[j]],T.query(id[j]+1)+quo[id[j]]);
i=rig;
}
T.clear();
for(int i=num;i>=1;--i){
int rig=i;
while(rig>1&&rem[id[rig-1]]==rem[id[i]]) --rig;
for(int j=i;j>=rig;--j) ckmax(nxt[id[j]],T.query(id[j]+1)+quo[id[j]]-1);
for(int j=i;j>=rig;--j) T.insert(id[j],now[id[j]]-quo[id[j]]);
i=rig;
}
//third type
bool updd=0;
for(int i=1;i<=num;++i) if(now[i]!=nxt[i]){updd=1; break;}
if(!updd) break;
if(turn==num) puts("No"),exit(0);
for(int i=1;i<=num;++i) now[i]=nxt[i],nxt[i]=-inf;
}
puts("Yes");
return 0;
}