【考试总结】2022-03-26
灯
使用经典容斥:联通块数等于亮色灯数量 \(-\) 相邻两个点都是亮点的点对数
将颜色视为点,两种不同颜色的相邻对数记作边权,变成图上问题
每次修改如果点度数不超过既定阀值 \(B\) 暴力扫描出边对边的状态进行修改,否则记录度数大的点和亮点的边权之和即可
我带了 \(\log\) 来得到小空间复杂度,不难发现这种方法大点的数量越少越好,所以块大小开到 \(B=2000\) 即可通过
谨防新型根号分治:不判断度数大于根号直接进行大点的处理
Code Display
const int N=1e5+10,B=2000;
int n,m,Q,col[N],cnt[N];
bool sta[N];
map<int,int> mp[N];
inline void ins(int x,int y){
if(x>y) swap(x,y);
mp[x][y]++; mp[y][x]++;
return ;
}
int lig[N];
signed main(){
freopen("light.in","r",stdin); freopen("light.out","w",stdout);
n=read(); m=read(); Q=read();
rep(i,1,n){
col[i]=read();
if(i!=1&&col[i]==col[i-1]) --i,--n;
else{
cnt[col[i]]++;
if(i>1) ins(col[i],col[i-1]);
}
}
vector<int> nds;
int ans=0;
rep(i,1,m) if(cnt[i]>B) nds.emplace_back(i);
vector<vector<pair<int,int> > >G(m+1);
for(int i=1;i<=m;++i){
G[i].resize(mp[i].size());
int j=0;
for(auto t:mp[i]) G[i][j++]=t;
}
while(Q--){
int x=read(),coef=sta[x]?-1:1;
ans+=coef*cnt[x];
if(cnt[x]<=B){
for(auto t:G[x]) if(sta[t.fir]) ans-=t.sec*coef;
}else{
ans-=coef*lig[x];
}
sta[x]^=1;
for(auto t:nds) if(mp[x].count(t)) lig[t]+=coef*mp[x][t];
print(ans); putchar('\n');
}
return 0;
}
十字路口
设每次观察的时间为 \(t_i\),那么可以根据每个灯红灯剩余时间得到 \(\Theta(m^2)\) 个形如 \(t_i-t_j=a_i-a_j\) 的方程
将每次观察建立点,将 \(a_i< a_j\) 的 \((i,j)\) 连边,使用 \(\rm Floyd\) 可以求出来最小环作为周期,复杂度 \(\Theta(m^3)\),在 \(n>m\) 时有效
否则可以将每个灯变成红灯的时间设为 \(x_i\),比对两次观察可以根据其它点在红灯里面的减少量得到方程
仍然使用图上连边的方式可以得到一个复杂度为 \(\Theta(n^3+n^2m)\),在 \(n\le m\) 的时候有效
两种做法揉起来即可,总复杂度 \(\Theta(nm\sqrt{nm})\)
Code Display
const int N=1e5+10,inf=0x3f3f3f3f3f3f3f3f;
int n,m;
vector<vector<int> >G;
map<int,int> app[N];
signed main(){
freopen("crossing.in","r",stdin); freopen("crossing.out","w",stdout);
n=read(); m=read();
G.resize(m+1);
for(int i=1;i<=m;++i) G[i].resize(n+1);
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j) G[i][j]=read();
bool rem=1;
for(int j=1;j<=n;++j){
if(app[j].count(G[i][j])){
int lin=app[j][G[i][j]];
for(int k=1;k<=n;++k) if(G[lin][k]!=G[i][k]) puts("-1"),exit(0);
rem=0;
break;
}
}
if(rem){
for(int j=1;j<=n;++j) if(G[i][j]) app[j][G[i][j]]=i;
}else --m,--i,G.pop_back();
}
int ans=inf;
if(m<=n){
vector<vector<int> > dp(m+1,vector<int>(m+1,inf));
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j) if(G[j][i]){
for(int k=1;k<=m;++k) if(j^k){
if(G[k][i]<G[j][i]) continue;
if(dp[j][k]==inf) dp[j][k]=G[k][i]-G[j][i];
}
}
}
for(int k=1;k<=m;++k){
for(int i=1;i<=m;++i) if(dp[i][k]!=inf){
for(int j=1;j<=m;++j) ckmin(dp[i][j],dp[i][k]+dp[k][j]);
}
}
for(int i=1;i<=m;++i) ckmin(ans,abs(dp[i][i]));
}else{
vector<vector<int> > dp(n+1,vector<int>(n+1,inf));
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j) if(G[i][j]){
for(int k=1;k<=n;++k) if(j^k){
if(G[i][k]<G[i][j]) continue;
if(dp[j][k]==inf) dp[j][k]=G[i][k]-G[i][j];
}
}
}
for(int k=1;k<=n;++k){
for(int i=1;i<=n;++i) if(dp[i][k]!=inf){
for(int j=1;j<=n;++j) ckmin(dp[i][j],dp[i][k]+dp[k][j]);
}
}
for(int i=1;i<=n;++i) ckmin(ans,abs(dp[i][i]));
}
print(ans==inf?-1:ans); putchar('\n');
return 0;
}
密室逃脱
注意到如果某个某个状态可以在 \(i\) 囤积若干个人,那么进行若干此操作之后仍然可以达成 “在 \(i\) 处囤积相同人数” 的目标,也就是说操作是可逆的
设 \(f_{i,j}\) 表示考虑前 \(i\) 个隧道处囤积 \(j\) 个人时,只考虑前 \(i\) 个隧道最多能放置的人数
这个状态巧妙地使用了上面的性质,那么转移有如下几条:
-
\(j\in[a_i,a_i+b_i)\),必须让 \(a_i\) 个人留到 \(i\) 隧道处,剩下的前进,转移至 \(f_{i+1,j-a_i}\)
-
\(j\ge a_i+b_i\),正向反向都可以顺利通过这个隧道,转移至 \(f_{i+1,j}\)
-
\(j\in [0,a[i])\) 通道是从左边打不开的,如果右边放置了 \(b_i\) 个人那么可以全员前进,否则右边放置的人数就是最多能到达 \(i+1\) 的人数,也就是以下两条
\[\begin{aligned}f_{i+1,j+b_i}&\leftarrow f_{i,j}+b_i\\f_{i+1,j}&\leftarrow f_{i,j}+k\ [k<b_i]\end{aligned} \]
注意第二维状态的上界,是 \(\max\{m,\max\{a_i+b_i\}\}\)
Code Display
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=1010,U=2e4;
int dp[N][20010],a[N],b[N];
int n,m;
signed main(){
freopen("escape.in","r",stdin); freopen("escape.out","w",stdout);
n=read(); m=read();
for(int i=1;i<n;++i) a[i]=read(),b[i]=read();
memset(dp,-0x3f,sizeof(dp));
rep(i,0,m-1) dp[1][i]=i;
for(int i=1;i<n;++i){
int Mx=-inf;
for(int j=0;j<a[i];++j){
ckmax(dp[i+1][j+b[i]],dp[i][j]+b[i]);
ckmax(Mx,dp[i][j]);
}
for(int j=0;j<b[i];++j) ckmax(dp[i+1][j],Mx+j);
for(int j=a[i];j<a[i]+b[i];++j) ckmax(dp[i+1][j-a[i]],dp[i][j]);
for(int j=a[i]+b[i];j<=U;++j) ckmax(dp[i+1][j],dp[i][j]);
}
int ans=-inf;
for(int i=0;i<=U;++i) ckmax(ans,dp[n][i]);
print(ans);
return 0;
}