2022高考集训3
2022高考集训3
6月7日
前言:
我们先写第三天的,因为第二天的我现在还有三道题没改完,笑死了,众生平等日。好了,姑且不去说它。6日晚上因为改题改不出来就吃巧克力,结果一不留神就吃了7,8块那种甜到发腻的白巧,结果晚上那个嗓子就崩溃了,我咽个唾沫都有一种灼烧感,一晚上就没睡好。早上起来都说不了话了。7号考试上午我就一直灌水,灌啊灌,然后接着灌,接着灌完继续灌。早上吃饭回来新放的一桶水,他们基本都没有怎么喝,让我自己干了半桶,当然效果是显著的,我现在嗓子已经不是那么疼了,但是感冒还没好且愈发严重了。。。。。
又因为对巧克力有恐惧了,加上ljx说byw特别喜欢吃巧克力,所以就中午就送了一盒给ljx,让他送给byw,我还特地给他那盒心形的没拆封的,这本来打算送myh的。byw也让ljx带坏了,两人见了我,都来一句王队。我就莫名有种感觉,byw跟着ljx喊王队的时候两个人真的很有夫妻相啊,我倒是挺祝愿他俩一直走下去的。扯远了,我们回去
因为一直灌水+上厕所,所以题也没有怎么好好写了,可能状态也确实是不好,最后还因为废弃的数组没删结果因为它re了40分
贴下成绩
rk8,可以说是非常不满意的,加上re的四十是rk3,但还是和rk1有差距,rk1AK了,又因为rk1是某人(好吧就是WinersRain),所以我不太想接受这个现实,就没截它,又因为第三天这次考得几道题洛谷上都有,所以就直接截图我自己博客就不加密了
T1
Watching Fireworks is Fun
这个题是个单调队列优化DP,首先可以看出是个DP,因为从不同的位置跑到某个位置获得的幸福度都是不一样的,肯定是要一层层的转移出来,他可不是越近越好,他可能这个近了,下个再跑就困难了,所以不能每次贪心的走最近,一定要dp去循环(这是351238提出的疑惑)。
dp[i][j]第i个烟花释放时在j位置可以获得的最大幸福,i只和i-1,可压一维
那么转移方程就是dp[i][j]=max(dp[i-1][j+-t*d])+幸福值,很明显,+-d*t的值是不变的,所以我们没有必要去开个k再循环,直接对于同一个i的不同j,单调队列维护一个max(dp[i-1][k])即可
复杂度是O(n*m)可过,我考场正反走两遍,j-d*t到j和j+d*t到j,这样滑动,我后来又想了想,其实没必要,绝对值就解决了,但后来也懒得改了,WinersRain好像就只扫了一遍,这个题一开始开了个wor数组以t为下标,是最开始得一种思路,后来改了也忘了删,结果t直接1e9那个数组re了卡我40分
查看代码
//Watching Fireworks is Fun
//啥也别想了,DP,而且单调优化
//这一秒转移过来不同的距离,单调优化
//这两周光做这个了,都快透了
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mx=1500000+1000;
int n,m;
ll d;
ll dp[5][mx];//dp[i][j]第i秒不对 是 (第i个烟花释放时)在j位置可以获得的最大幸福,i只和i-1,压一维
struct Node{
ll a;
ll b;
ll t;
}fw[mx];
int wor[mx];
int wor1[mx];
bool vis[mx];
ll sum[mx];//单调优化老数组了
int q[mx];
int l=1,r=0;
ll ans=9999999999999;//上次我可吃大亏了
//总不能真万亿幸福吧直接升天?
void Solve(){
scanf("%d%d%lld",&n,&m,&d);
int T=0;
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=m;++i){
ll a,b,t;
scanf("%lld%lld%lld",&a,&b,&t);
fw[i].a=a;
fw[i].b=b;
fw[i].t=t;
// wor[t]=b;
// wor1[t]=a;
// T=max(T,t);
sum[i]=sum[i-1]+b;
// printf("%lld\n",sum[i]);
}
for(int i=1;i<=n;++i){
dp[1][i]=abs(fw[1].a-i);
}
int pd=0;
int pp=1;
for(int i=2;i<=m;++i){
if(i%2==0){
pd=2;
pp=1;
}
else {
pp=2;
pd=1;//这个操作属实不符合我的水平
}
memset(dp[pd],0x3f,sizeof(dp[pd]));//这里千万被忘啊啊啊啊啊啊啊
ll x=(fw[i].t-fw[i-1].t)*d;//用烟花轮,可跨越的距离
l=1,r=0;
//每次都必须轮j,m*n大概可以
for(int j=1;j<=n;++j){
while(l<=r && j-q[l]>x)l++;//先距离
while(l<=r && dp[pp][j]<=dp[pp][q[r]])r--;
q[++r]=j;
dp[pd][j]=min(dp[pd][j],dp[pp][q[l]]+abs(fw[i].a-j));
}
l=1,r=0;
//这里有最大跳跃距离那个题的思想
//他可以从前从后两次来,看着一样,其实不太一样,大概不太一样
//总不能是我dp定义丑了吧,不能吧
for(int j=n;j>=1;--j){//其实据我推算,倒序的话,应该直接一维就行
//但是怎么说,怕写挂不是,还是开个二维
while(l<=r && q[l]-j>x)l++;
while(l<=r && dp[pp][q[r]]>=dp[pp][j])r--;
q[++r]=j;
dp[pd][j]=min(dp[pd][j],dp[pp][q[l]]+abs(fw[i].a-j));
}
}
int en=0;
if(m%2==0)en=2;
//这里真的很丑啊,一定学二进制
else en=1;
for(int i=1;i<=n;++i){
ans=min(ans,dp[en][i]);
}
ll an=sum[m]-ans;
// printf("%lld ans=%lld\n",sum[m],ans);
printf("%lld\n",an);
//我样例直接1A,别这样,我害怕
/* for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
//那这个d就要乘上时间t了
//wor是不变的,变的是k
//不对啊
//md这是第一题的难度嘛
//奶牛那个题,正难则反
//我不去维护答案,我去维护,奶牛要工作
//所以维护工作损耗,我要幸福,所以维护幸福损耗
//就直接维护ai-x呗,同奶牛一样,最后的总要求b是一样的
dp[i][j]=max(dp[i-1][j+ -d])
//+ -d 在j-d <= k <=j+d 里维护一个k
//它+d其实应该再倒着来一遍,不然不太好写
}
} */
/* for(int i=1;i<=T;++i){
for(int j=1;j<=n;++j){ //t*n还是离谱,就是得拿烟花轮
for(int k=j-d;k<=j+d;++k){//拿时间跳?拿烟花跳?
dp[i][j]=max(dp[i][j],dp[i-1][k]+wor[i-1]-abs(wor1[i-1]));//就这?????????
}
}
}*/
}
int main(){
// freopen("data.in","r",stdin);
// freopen("out.out","w",stdout);
freopen("fire.in","r",stdin);
freopen("fire.out","w",stdout);
Solve();
return 0;
}
我再贴下WinersRain的
查看代码
#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;
}
T2
Perform巡回演出
我当时考场做嗨了,应该是2,4简单,1,3难,A了2,4的不少,A 1,3的没几个,我是除了rk1之外唯一一个把1,3A了的人,主要T3有意思,我就做了两个小时,回来也没细细分析就直接去做T1了,没想到T2,T4这么简单
这个没啥好所的,一个普通DP,显然出的东西,就是题面挺复杂,我考后改的时候因为dp[][]数组的i,j大小和定义反了,就一直改不过去,气死了
查看代码
//Perform巡回演出
//“艺术家”!
//这没有办法跑最短路,所以只能是个DP
//而且n<=10,但状压也不好写啊,那就应该是个普通
//dp[i][j]= 第i天在城市j的最小值?
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mx=4000;
struct Node{
int d;
int moy[40];
//20*20*40=400*40=16000 ok
}fly[50][2000];
ll dp[1010][20];//也是,这一天只和上一天有关,压一维 ,time也不大,没有必要压
void clear(){
memset(dp,0,sizeof(dp));
//fly不清空大概没有问题吧,我说应该
}
void Solve(){
int cnt=0;
// printf("kkkkkkkkkk\n");
while(1){
cnt++;
int n,k;
scanf("%d%d",&n,&k);
if(n==0 && k==0)break;
// auto nn=n*(n-1);
for(auto i=1;i<=n;++i){
int pd=0;
for(auto j=1;j<=n-1;++j){
// printf("j=%d\n",j);
auto jj=j;
if(pd==1)jj++;
if(i==jj){
pd=1;
jj++;
}
int d;
scanf("%d",&d);
// printf("kk %d\n",d);
fly[i][jj].d=d;
// printf("f[%d][%d]=%d\n",i,jj,fly[i][jj].d);
for(auto k=1;k<=d;++k){
int x;
scanf("%d",&x);
// printf("kkk %d\n",x);
fly[i][jj].moy[k]=x;
}
}
// printf("i=%d\n",i);
}
//这个题怎么这么简单啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
//但凡我没有做T3做两个半小时
//这个题这不秒切?
//后悔了,应该先做T2,再做T4,再T1,T3
//而不是切完T3,T1之后 连分析T2的时间都没有
memset(dp,0x3f,sizeof(dp));
// dp[1][1]=0;
for(int i=2;i<=n;++i){
if(fly[1][i].moy[1]==0)continue;
dp[1][i]=fly[1][i].moy[1];
}
for(auto i=2;i<=k;++i){
// printf("kkk i=%d\n",i);
for(auto j=1;j<=n;++j){
// printf("kk j=%d\n",j);
for(auto k=1;k<=n;++k){
// printf("ooo k=%d\n",k);
if(k==j)continue;
// if(fly[k][j])
// printf("pppppppppppppppppp\n");
// printf("i=%d fly[k]=%d\n",i,fly[k][j].d);
int t=i%fly[k][j].d;
// printf("kkkkkkkkkkkkkkk\n");
if(t==0)t=fly[k][j].d;
if(fly[k][j].moy[t]==0)continue;
dp[i][j]=min(dp[i][j],dp[i-1][k]+fly[k][j].moy[t]);
// printf("dp[%d][%d]=%lld\n",i,j,dp[i][j]);
}
}
}
// printf("kkkkkkkkkkk\n");
ll anss=99999999999;
auto ans=anss;//auto 不太能通过具体的数值推导到ll,它只能是l int
/* for(int i=1;i<=n;++i){
ans=min(dp[k][i],ans);
}*/
ans=dp[k][n];
if(ans>=99999999){
printf("0\n");
}
else printf("%lld\n",ans);
}
}
int main(){
freopen("perform.in","r",stdin);
freopen("perform.out","w",stdout);
Solve();
return 0;
}
T3
枪战Maf
这个题我一看就兴趣来了,然后瞎搞了两个多小时搞出来了。下午讲题的时候我特地跟WinersRain说我想讲这个题,干饭了下午再写。首先,依据瞄准关系建个图,没有入度的点一定不会死,那么同样,他指向的人一定会死。从没有入度的点开始,求最大他最后开枪求最小他第一个开枪。我们又可得,如果是一个环,最少要死一半(间隔开枪,且这个枪一定要响),最多死得只剩下一个。这就是模拟的思路。把模拟进行优化,分析发现,图中不会出现一条链的情况,因为每个人必定指向一个人(哪怕是指自己),图中要么是环,要么是链加环,或者这两者结合,但基本图就是这两种。
一个环:n/2和1
一个链+环:环上的点可以全死 链上的点依次记录即可。
我们完全可以从入度为零的点开始跑一个拓扑,(有点像集训第一天的T4那个题),从入度为零的点开始依次删点,删完之后图中就只会剩下环了。有一个区别就是奇数点链加环会被拓扑消解掉,偶数点链加环,它的环不会被消解,但它的环最多可以死的一个都剩,所以要与普通环区分一下
这个代码里说了很多废话,主要我想起玩过的一局狼人杀,奇迹商人板子,我不理解我明明四阶不低了,为什么还会碰到sb商人给幸运儿技能给的是枪,然后那sb幸运儿把我枪了说自己以为自己是猎人,就认为我是狼,我气炸了,我说我是纯白之女行了吧hhhhhhc
查看代码
//枪战Maf
//愿世界和平 阿门
//执枪的人,一定要做好被杀的觉悟 ---鲁鲁修
//所以不杀人的都先给爷死hhhhc
//只有杀人才有可能活下来是这意思
//当然成个环还是都得死
//所以世事纷纷恩怨何时了啊
//远了远了
//建图,没有入度不会死
//从没有入度的点开始,求最大他最后开枪
//求最小他第一个开枪
//有环最少要死一半(因为这个枪一定要响) ,最多死剩下一个,看谁枪快呗
//找环用并查集?
//一个时间只能有一位幸运儿开枪,问纯白之女有多大概率被幸运儿毙掉hhhhhc,商人狂喜
//(我这辈子比较后悔的一件事大概就是误以为鲁鲁修是热血王道漫,结果全tm是刀,都给孩子刀傻了)
//所以最大怎么处理啊啊啊啊啊啊啊啊啊啊啊
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mx=3000000+1000;
int n;
int head[mx];
int len;
int an1,an2;//记录被杀还不如记录存话
int q[mx];
int l=1,r=0;
bool vis[mx];//可能一个人被毙了一次后又被毙
struct Node{
int from;
int to;
int next;
}e[mx];
int in[mx];//被杀
int aim[mx];
int save[mx];
void Insert(int u,int v){
e[++len].from=u;
e[len].to=v;
e[len].next=head[u];
head[u]=len;
}
void Solve(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
int x;
scanf("%d",&x);
aim[i]=x;
in[x]++;
// Insert(i,x);//不用建图啊,它一定是一指一的
}
for(int i=1;i<=n;++i){
if(in[i]==0){
an1++;
an2++;
q[++r]=i;
/* for(int j=head[i];j;j=e[j].next){//它不会死,它指的这个人必死
//如果求最小,这个人指的所有人的in都要减1
//如果求最大不好搞啊,少死了几个也不好说
q[++r]=e[j].to;//这些人是必死的
//这不又是前天t4的拓扑?
}*/
}
//那while得拿出来
}
//printf()
while(l<=r){
int u=q[l];
l++;
// for(int j=head[u];j;j=e[j].next){//这些人是必死的
int v=aim[u];
if(vis[v]==1)continue;//这里真是又细又恶心
vis[v]=1;
int vv=aim[v];
in[vv]--;
save[vv]=1;
if(in[vv]==0){
//它能不死就不死
an2++;//所以记录一下存活最大人数
q[++r]=vv;//我得用队列记录不死而不是死
// printf("vv=%d r=%d\n",vv,r);
}
// }
} //最大不好求,算了,先放着
//此刻图中只会有环了,因为已经将所有入度为零的点和他指向的人
//删了
//其实不用并查集,因为这个图极其简单
//用aim转回来就行了,其实也是建了个图
// printf("r=%d a2=%d\n",r,an2);
int huan=0;
for(int i=1;i<=n;++i){
if(in[i]!=0 && vis[i]==0){
// printf("i=%d\n",i);
int v=i;
int pd=0;
huan=0;
while(1){
if(vis[v]==1)break;
vis[v]=1;
v=aim[v];
huan++;
if(save[v]==1)pd=1;
}
if(pd==0 && huan>1)an1++;//这下没问题了
// an1+=1;
//这里有问题,就是,它可能一个都不活,
//就是有这么一种情况,环外绝对活指向一个环
//这会导致这个环被拆完,这好像没问题
//不是这,an1不是直接加1,它有的人是因为
// 瞄准他的人死了才进入环,他可能两个人指一个人 所以这个环里不能有人
//被环外的人瞄准,an1才能+
an2+=huan/2;//最多活一半真惨
//是向下取整没有毛病
}
}
int d1=n-an1;
int d2=n-an2;
printf("%d %d\n",d2,d1);
}
int main(){
// freopen("data.in","r",stdin);
// freopen("out.out","w",stdout);
freopen("maf.in","r",stdin);
freopen("maf.out","w",stdout);
Solve();
return 0;
}
T4
翻转游戏
这个题说实话属实是没有什么水平,当然我没做出来,我更没水平。用状压的思想去枚举虚拟第零行的状态,然后暴力修改,看是否符合。枚举完取个min最小修改次数。就是二进制运用的挺巧妙,我这代码抄的谁的我忘了
查看代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int mx=300;
char s[mx][mx];
int f[mx][mx];
int ans=1000000;
int n;
void updata(int x,int y){
if(f[x][y]==1)f[x][y]=0;//这里那题解用 ^=1,属实是我水平不够了
else f[x][y]=1;
if(f[x-1][y]==1)f[x-1][y]=0;
else f[x-1][y]=1;
if(f[x+1][y]==1)f[x+1][y]=0;
else f[x+1][y]=1;
if(f[x][y-1]==1)f[x][y-1]=0;
else f[x][y-1]=1;
if(f[x][y+1]==1)f[x][y+1]=0;
else f[x][y+1]=1;//这么修改属实是暴力
}
void dfs(int now,int step,int to){
if(now==n+1){//深搜更新答案
for(int i=1;i<=n;++i){
if(f[n][i]!=to)return ;
}
ans=step<ans?step:ans;//三目运算真帅,以后要逐渐学了
//通过枚举16种状态就解决了我疑惑的最小问题
return;
}
int pos=0;
for(int i=1;i<=n;++i){
if(f[now-1][i]!=to){
step++;
updata(now,i);
pos|=1<<(i-1);
//这个 或运算 在这里就记录的了我们在什么位置有修改
//下面再还原只需要把pos再或回去就行
//其实还是非常妙的,二进制一定要多学
//当然我现在理解不太深刻,回头再学,一定学
}
}
dfs(now+1,step,to);
for(int i=1;i<=n;++i){
if(pos>>(i-1)&1)updata(now,i);
}
}
void Solve(){
char c;
n=4;
int pd=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
scanf(" %c",&s[i][j]);
if(s[i][j]=='b')f[i][j]=1;
else f[i][j]=0;
if(i==1 && j==1)c=s[i][j];
if(c!=s[i][j])pd=0;
}
}
//通过调整下一行来更改上一行,最后一行决定行不行
//同时第一行本身怎么换也要枚举,所以加一个第零行,一共有16种情况
//依次来逐个修改,所以正好学学状压dp
if(pd==1){
printf("0\n");
return ;
}
for(int k=0;k<(1<<n);++k){//枚举第零行的所有状态
for(int i=1;i<=n;++i){
f[0][i]=(k>>(i-1))&1?1:0;//这就把第0行的状态赋值出来了
//k右移i-1为就是这第i位,&1成立是1,否则0,二进制加三目运算太帅了
}
dfs(1,0,0);//一:第几行 二:第几步 三:目标是全转换成谁
dfs(1,0,1);
}
if(ans>=10000)printf("Impossible\n");
else printf("%d\n",ans);
}
int main(){
freopen("flip.in","r",stdin);
freopen("flip.out","w",stdout);
Solve();
return 0;
}
后记:
集训的快乐时光眨眼就过去了,之后就又是令人绝望的期末与文化课,唉,只要心中尚存有一丝希望,那便坚定不渝的追寻吧。纵使前路位置,一切渺茫,但总会有些东西,一直未变不是么