[游记]2022高考集训3-2022.6.7
今天高考Day1
此乃国之大典,此乃士之生死场
别中二了
A. Watching Fireworks is Fun
B. Perform巡回演出
C. 枪战Maf
D. 翻转游戏
赛时得分:$400/400$ ,用时两个半小时,顺便在代码里写了不少注释(
而且我做题顺序是难度倒序我不理解
剩下的时间看了一会字符串,写了一个小时焚化课(
写了一套半数学卷子(
赛时排行:显然 $\mathit{Rank1}$
jijidawang酱是rk2太强了%%%jijidawang又小又强又可爱(
开赛看到比赛说明:难度可能不按顺序
略作沉思开T1:你好动规
略作沉思开T2:连题都读不懂
略作沉思开T3:好像很可做
C. 枪战Maf
赛后发现我眼光独到一眼叨中最难的题
然而一开始并没有发现这题不可做
然后发现环里面的有趣的性质
想了想用队列维护一下入度为0的点
于是就过了
1. 对于每个连通图,他至少有一个环或者一个人自杀。缩完点之后它是树或树林。(其实是基环树的森林
证明:假设我们已 经输入了n个人,先忽略一下第n个人要杀谁,
如果之前没有人自杀或环的话它一定是一棵树,因为 有n个点和n-1条边,那么第n个人指向哪里就很关键了,
他指向每一个其他人都会形成一个环,而 指向他自己又是自杀,所以,第一条get。
2. 每一个没人杀的人都会存活,每一个自杀的人最终都会死。不解释。
3. 每个人对于答案的影响不在于他被谁杀掉,而在于他杀掉谁,世界不关心你说了什么,世界只关心 你做了什么。
4. 一个环如果没人干预,那么它最少死n/2个人,向下取整。也就是一个人如果不杀人就只能被杀 死,对于奇数个点的环你杀了人也可能被杀死。最多就是总人数-1。
5. 一个环如果有人干预,那么它最多就是全部被杀,最小在下面说。
6. 如果一个人没死,那么他指向的那个人就一定会死,如果指向那个人的人都死了,那么他就可以活 下来,这一点在环中同样适用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<string> #define WR WinterRain #define int long long #define Maf signed using namespace std; const int WR=1001000; int n,aim[WR]; int ipt[WR]; int lve,sze; int que[WR],pos; bool kill[WR],exist[WR]; int read(){ int s=0,w=1; char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ s=(s<<3)+(s<<1)+ch-48; ch=getchar(); } return s*w; } Maf main(){ freopen("maf.in","r",stdin); freopen("maf.out","w",stdout); n=read(); for(int i=1;i<=n;i++) aim[i]=read(),ipt[aim[i]]++; for(int i=1;i<=n;i++){//这里我们记录存活人数因为死亡人数实在是不好写 if(ipt[i]==0){//没人射我我一定可以活 lve++; que[++sze]=i; } } pos=1; while(pos<=sze){//que队列维护入度是0的点 int frnt=que[pos];//取出队首 pos++; if(kill[aim[frnt]]) continue;//如果目标已经被杀了 kill[aim[frnt]]=true;//否则目标一定被杀 int targ=aim[aim[frnt]]; //targ表示当前节点目标的目标 ipt[targ]--;//targ不一定死但也不一定活 exist[targ]=true;//如果我们追求杀的人最多那么targ一定得死 //但如果我们佛系那么targ一定能活 if(!ipt[targ]) que[++sze]=targ;//这里注意lve不用加 //因为lve是铁定能活的 } for(int i=1;i<=n;i++){ if(ipt[i]&&!kill[i]){ int ring=0;//找一下环 bool flag=false; for(int j=i;!kill[j];j=aim[j]){ ring++; if(exist[j]) flag=true;//这里标记”可以死“,如果有标记直接全都死 kill[j]=true;//有自环 } if(!flag&&ring>1) lve++;//ring=1是自环 sze+=ring/2;//最少也得有n/2向上取整个人死掉(悲 } } printf("%lld %lld",n-sze,n-lve); fclose(stdin); fclose(stdout); return 0; }
然后一个小时就这样过去了
看了看 $T1$ 发现好像像昨天 $T4$ 一样也要用到优化
Update: 感谢 @jijidawang 酱指正,和昨天 $T4$ 的解法没有关系
然鹅并没有昨天那样恶心
A. Watching Fireworks is Fun
于是我就首先写了两道最难的题目……(
考虑 $dp$ 数组维护在某个位置的最大值
由于空间不够只能滚掉一维
状转式子好想,是 $dp[i][j] = dp[k][j-1] + val[j]$
这里 $x$ 和 $i$ 之间的距离要在时间内走的到才能转移 ,$val$ 表示能够获得的价值
然后发现时间复杂度有点高会挂,考虑单调队列优化
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<string> #define WR WinterRain #define int long long #define Dream signed using namespace std; const int WR=1001000,INF=1152921504606846976;//皮一下2^60 struct FireWork{ int a,b,t; }fire[WR]; int n,m,d; int ans; int tmp[WR],dp[WR]; int que[WR]; int read(){ int s=0,w=1; char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ s=(s<<3)+(s<<1)+ch-48; ch=getchar(); } return s*w; } Dream main(){//想起Dream那次烟花火箭送Muffin Team上天 freopen("fire.in","r",stdin); freopen("fire.out","w",stdout); n=read(),m=read(),d=read(); for(int i=1;i<=m;i++){ fire[i].a=read(),fire[i].b=read(),fire[i].t=read(); } for(int i=1;i<=m;i++){ memcpy(tmp,dp,sizeof(dp));//本来我想开循环赋值的 //但是想到上次T2爆零 //爷即使TLE也不用循环了 if(fire[i].t==fire[1].t){//其实看着后面的单调队列这个有点多余了 for(int j=1;j<=n;j++){//但是以防万一加上吧 dp[j]=tmp[j]+fire[i].b-abs(fire[i].a-j); } continue; } //这么一看普通动规还过不了n*m^2 //希望我的复杂度算的没错( //得单调队列优化???别吧 int l=1,r=0; int k=1;//k表示在哪个位置 int dis=(fire[i].t-fire[i-1].t)*d;//dis表示在两次烟花之间可以跑多远 for(int j=1;j<=n;j++){ while(k<=min(dis+j,n)){//总不能跑出去吧 while(l<=r&&tmp[k]>=tmp[que[r]]) r--;//单调队列存储的是地点 que[++r]=k;//维护一个递减的单调队列 k++; }//我害怕它会被卡成n^2...... while(l<=r&&j-dis>que[l]) l++;//必须能从j点跑到l dp[j]=tmp[que[l]]+fire[i].b-abs(fire[i].a-j); } } ans=-INF; for(int i=1;i<=n;i++) ans=max(ans,dp[i]); printf("%lld",ans); fclose(stdin); fclose(stdout); return 0; }
因为昨天 $T4$ 改了所以还能做出来……
为啥有的大佬赛后写出来了昨天 $T4$ 这道弱化版不会做啊?这……我也说不清
然后看 $T4$
好像是一道小清新的模拟
D. 翻转游戏
一开始可以发现对于一列上单独的一格,我们让它下一列的对应格修改,这样可以达到 “只变这一列的某个特定格子,这一列的其他格子不变” 的目的
这样我们可以扫每个格子,对应修改上一列不是目标状态的格子
然后到了最后一列如果不是全都是指定颜色就输出 $Impossible$
但是发现这样是不行的,我们还需要修改第一行
于是我们考虑添加第 $0$ 行,枚举行状态,让第零行也被清成目标状态即可
不用担心时间复杂度,$n = 4$ 你担心什么时间复杂度
就没了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<string> #include<iostream> #define WR WinterRain #define int long long #define Flip signed //我在担心个锤子的时间复杂度啊 //这题2^2^n都能跑过 using namespace std; const int n=4,WR=101,INF=2147483647; int ans; int mp[WR][WR]; int read(){ int s=0,w=1; char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ s=(s<<3)+(s<<1)+ch-48; ch=getchar(); } return s*w; } void flip(int x,int y){ mp[x-1][y]^=1; mp[x+1][y]^=1; mp[x][y-1]^=1; mp[x][y+1]^=1; mp[x][y]^=1; } void dfs(int lne,int stp,int opt){ if(lne==n+1){ bool flag=true; for(int i=1;i<=n;i++){ if(mp[n][i]!=opt) flag=false; } if(flag) ans=min(ans,stp); return; } //不想再复制粘贴一遍了 //有没有什么方法可以让我记录操作? //-------------------------------------- //还是状压 int s=0; for(int i=1;i<=n;i++){ if(mp[lne-1][i]!=opt){ flip(lne,i);//发现并非目标状态就翻转 s|=(1<<(i-1)); stp++; } } dfs(lne+1,stp,opt); for(int i=1;i<=n;i++){ if(s&(1<<(i-1))) flip(lne,i);//这样就能复原了 //因为翻两次等于没翻 } } Flip main(){ freopen("flip.in","r",stdin); freopen("flip.out","w",stdout); char ch; for(int i=1;i<=n;i++){//这个4太不舒服了......换个n打着也丝滑 for(int j=1;j<=n;j++){ cin>>ch; if(ch=='b') mp[i][j]=1; else mp[i][j]=0; } } //我个人认为显然的可以把上面一行扔到下面一行处理 //这样可以在不改变上一行其它棋子的情况下把上一行化成目标状态 //额......那第一行怎么办,我可以让第一行翻转啊 //---------------------------------------------------------- //可不可以设第0行? //但枚举第0行状态会不会爆时间啊 //---------------------------------------------------------- //你脑子抽fang了顶多乘16爆个锤子时间啊 ans=INF; for(int s=0;s<(1<<n);s++){ for(int i=1;i<=n;i++){ if(s&(1<<(i-1))) mp[0][i]=1; else mp[0][i]=0; } dfs(1,0,0);//从第一行找全白子 dfs(1,0,1);//从第一行找全黑子 } if(ans==INF) printf("Impossible"); else printf("%lld",ans); fclose(stdin); fclose(stdout); return 0; }
此时过去两个小时开 $T2$
B. Perform巡回演出
人话题意:给你一个 $n\times n-1$ 的表,其中的每个元素是航班花费
航班花费是一组循环节大小为 $d$ 的数,在输入时给出
现在求从 $1$ 到 $n$ 经过 $k$ 天的花费最小
注意不能停留
那这道题就简单了
枚举一下转移点就好了啦
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<string> #define WR WinterRain #define int long long #define LuBenWei signed using namespace std; const int WR=110; int n,k; int flight[WR][WR][WR*10]; int dp[WR*20][WR*20]; int read(){ int s=0,w=1; char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ s=(s<<3)+(s<<1)+ch-48; ch=getchar(); } return s*w; } LuBenWei main(){//既然是乘坐飞机那么岂有不提卢本伟之理? freopen("perform.in","r",stdin); freopen("perform.out","w",stdout); n=read(),k=read(); //???A题刚来一个动规T2又来一个??? //合着今天就DP专题呗 //可惜我不像wonder大佬一样会记忆化搜索不然我一定爆搜 while(k!=0&&n!=0){//我的天这输入就已经够恶心的了 memset(dp,0x3f,sizeof(dp));//《memset》 int tmp=dp[k][n];//希望flight数组不用初始化 //事实应该也的确不用 for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i!=j){ int d=read(); for(int l=1;l<=d;l++){ flight[i][j][l]=read(); } for(int l=d+1;l<=k;l++){ flight[i][j][l]=flight[i][j][l-d]; }//这个循环节莫名其妙 }//这也没什么难度...... } } for(int i=2;i<=n;i++){//如果发现可以从城市1直达 if(flight[1][i][1]!=0) dp[1][i]=flight[1][i][1]; }//那么第一天在i城市就直接初始化了 for(int i=2;i<=k;i++){ for(int j=1;j<=n;j++){ for(int l=1;l<=n;l++){//复杂度岌岌可危 //???n<=10那我慌什么 if(j==l||flight[l][j][i]==0) continue; //参考Floyd,找一下转移点 dp[i][j]=min(dp[i][j],dp[i-1][l]+flight[l][j][i]); //可以从第l个点转移过来 } } }//那这也没什么难的 if(dp[k][n]==tmp) printf("0\n"); else printf("%lld\n",dp[k][n]); n=read(),k=read(); } fclose(stdin); fclose(stdout); return 0; }
于是AK了非常感动
今后也要继续努力!
本文来自博客园,作者:冬天丶的雨,转载请注明原文链接:https://www.cnblogs.com/WintersRain/p/16351898.html
为了一切不改变的理想,为了改变不理想的一切