[游记]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. 如果一个人没死,那么他指向的那个人就一定会死,如果指向那个人的人都死了,那么他就可以活 下来,这一点在环中同样适用。

#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;
}
AC代码

然后一个小时就这样过去了

看了看 $T1$ 发现好像像昨天 $T4$ 一样也要用到优化

Update: 感谢 @jijidawang 酱指正,和昨天 $T4$ 的解法没有关系

然鹅并没有昨天那样恶心

A. Watching Fireworks is Fun

 

 

 于是我就首先写了两道最难的题目……(

考虑 $dp$ 数组维护在某个位置的最大值

由于空间不够只能滚掉一维

状转式子好想,是 $dp[i][j] = dp[k][j-1] + val[j]$

这里 $x$ 和 $i$ 之间的距离要在时间内走的到才能转移 ,$val$ 表示能够获得的价值

然后发现时间复杂度有点高会挂,考虑单调队列优化

#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;
}
AC代码

因为昨天 $T4$ 改了所以还能做出来……

为啥有的大佬赛后写出来了昨天 $T4$ 这道弱化版不会做啊?这……我也说不清

然后看 $T4$ 

好像是一道小清新的模拟

D. 翻转游戏

 

 

 

一开始可以发现对于一列上单独的一格,我们让它下一列的对应格修改,这样可以达到 “只变这一列的某个特定格子,这一列的其他格子不变” 的目的

这样我们可以扫每个格子,对应修改上一列不是目标状态的格子

然后到了最后一列如果不是全都是指定颜色就输出 $Impossible$

但是发现这样是不行的,我们还需要修改第一行

于是我们考虑添加第 $0$ 行,枚举行状态,让第零行也被清成目标状态即可

不用担心时间复杂度,$n = 4$ 你担心什么时间复杂度

就没了

#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;
}
AC代码

 

此时过去两个小时开 $T2$

B. Perform巡回演出

 

 

 

 

 

 

 人话题意:给你一个 $n\times n-1$ 的表,其中的每个元素是航班花费

航班花费是一组循环节大小为 $d$ 的数,在输入时给出

现在求从 $1$ 到 $n$ 经过 $k$ 天的花费最小

注意不能停留

那这道题就简单了

枚举一下转移点就好了啦

#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;
}
AC代码

 

于是AK了非常感动

今后也要继续努力!

posted @ 2022-06-07 15:15  冬天丶的雨  阅读(102)  评论(10编辑  收藏  举报
Live2D