【考试总结】2022-03-26
灯
使用经典容斥:联通块数等于亮色灯数量 相邻两个点都是亮点的点对数
将颜色视为点,两种不同颜色的相邻对数记作边权,变成图上问题
每次修改如果点度数不超过既定阀值 暴力扫描出边对边的状态进行修改,否则记录度数大的点和亮点的边权之和即可
我带了 来得到小空间复杂度,不难发现这种方法大点的数量越少越好,所以块大小开到 即可通过
谨防新型根号分治:不判断度数大于根号直接进行大点的处理
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;
}
十字路口
设每次观察的时间为 ,那么可以根据每个灯红灯剩余时间得到 个形如 的方程
将每次观察建立点,将 的 连边,使用 可以求出来最小环作为周期,复杂度 ,在 时有效
否则可以将每个灯变成红灯的时间设为 ,比对两次观察可以根据其它点在红灯里面的减少量得到方程
仍然使用图上连边的方式可以得到一个复杂度为 ,在 的时候有效
两种做法揉起来即可,总复杂度
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;
}
密室逃脱
注意到如果某个某个状态可以在 囤积若干个人,那么进行若干此操作之后仍然可以达成 “在 处囤积相同人数” 的目标,也就是说操作是可逆的
设 表示考虑前 个隧道处囤积 个人时,只考虑前 个隧道最多能放置的人数
这个状态巧妙地使用了上面的性质,那么转移有如下几条:
-
,必须让 个人留到 隧道处,剩下的前进,转移至
-
,正向反向都可以顺利通过这个隧道,转移至
-
通道是从左边打不开的,如果右边放置了 个人那么可以全员前进,否则右边放置的人数就是最多能到达 的人数,也就是以下两条
注意第二维状态的上界,是
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律