[游记]2022高考集训1-2022.6.5

好像是 ZhengRuiOI 2021 CSP 7 连测 day2 ?

A层的大佬们进来比赛了有点慌

肯定前十全是A层Dalao罢

考的又是一堆字符串鬼题

对于我这个不会字符串的蒟蒻抱有极大的恶意

所以不出所料炸了

A. 2A

B. 2B

C. 2C

D. 2D

这名字真整(

赛时得分:300/400

赛时排行:Rank5 

Update:经过LyinMX大佬和artalter大佬的不懈努力,增加了两组Hack

排名有了一点小小的调整

原排名:

 

真实排名:

虽然排名好像没变

 %%%Eafoo太强了AK了,%%%CDsidi大佬,%%%Chen_(一点都不)jr大佬,%%%Lyin大佬把我吊着打

(话说这几位大佬都是A层的……太强了%%%)

赛时先看T1

A. 2A

 

 先吐槽一下题目与内容严重不符,RNM退钱

言归正传,这种暴力模拟恶心就在于难于判重

  1. 注意连续出现好多个点12.....12..12.12
  2. 注意有前导0
  3. 注意000.000.000.000这种奇葩情况
  4. ……

大概也就这点了,大佬们考虑加一些Hack数据

先加着,如果卡掉了我我再改

Update:好像真的卡掉了改一下改一下

Hack: 100000000000000000000.0.0.0 (1e20)

丧心病狂的卡 long long

 

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#define WR WinterRain
#define int long long
#define IP signed
using namespace std;
const int WR=100;
char str[WR];
int len,ptlen;
int ad[WR],cnt;
bool flag,pre,tag;
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;
}
IP main(){
    freopen("ip.in","r",stdin);
    freopen("ip.out","w",stdout);
    cin>>str;
    len=strlen(str);
    flag=true,pre=false,tag=false;
    for(int i=0;i<len;i++){
        if(str[i]>='0'&&str[i]<='9'){
            if(!pre){
                pre=true;
                cnt++;
                if(str[i]=='0') tag=true;
            }
            ptlen++;
            ad[cnt]=ad[cnt]*10+str[i]-48;
            if(ad[cnt]>255){
                ad[cnt]=255,flag=false;
            }//这里,善良的artalter卡爆了long long
        }else{
            if((ad[cnt]>255)||//判断是否越界
               (tag&&ad[cnt]!=0)||//判断是否出现前导0
               (ptlen>1&&!ad[cnt])||//判断000.000.000.000这个鬼
               (str[i]!='.')||//判断间隔符是不是'.'
               ((str[i-1]>='0'&&str[i-1]<='9')^(str[i+1]>='0'&&str[i+1]<='9'))){
                flag=false;//还要判分隔符两边是不是数字
            }
            pre=false,tag=false,ptlen=0;
        }
    }
    if(cnt!=4||(tag&&ad[cnt]!=0)) flag=false;
    if(flag){
        printf("YES");
        fclose(stdin);
        fclose(stdout);
        return 0;
    }else printf("NO\n");
    for(int i=1;i<=4;i++){
        if(ad[i]>255) ad[i]=255;
    }
    for(int i=1;i<=3;i++){
        printf("%d.",ad[i]);
    }
    printf("%d",ad[4]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}            
AC代码

 

 

但总归是十分简单的

然后开T2:怎么又是字符串?

B. 2B

额这个名字……

 

 

 ……

显然的我们可以发现A其实是没有用的,有用的是P

自然我们想让被弹掉的A尽可能多

于是我们可以从前向后扫一遍,发现AP就弹掉

剩下的PP再弹

(这不比T1简单

 

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#define WR WinterRain
#define int long long
#define APstr signed
using namespace std;
const int WR=1001000;
char str[WR];
int acnt,pcnt;
int len;
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;
}
APstr main(){
    freopen("apstr.in","r",stdin);
    freopen("apstr.out","w",stdout);
    cin>>str;
    len=strlen(str);
    for(int i=0;i<len;i++){
        if(str[i]=='A') acnt++;
        else if(acnt>0) acnt--;
        else pcnt++;
    }
    printf("%lld",acnt+(pcnt%2));
    fclose(stdin);
    fclose(stdout);
    return 0;
}
AC代码

 

 

很快切了看T3

C. 2C

额……怎么还是字符串???

 

 

 

 

 

 

 

 

 

 

首先考虑是否可以用map映射不然会出人命的

然后考虑菱形的概念是什么?

数据里有一个 1决定2,1和2决定3,1、2和3共同决定4……

这不属于菱形:具体原因见第3条,类X不是从Y派生的,类Y也不是从类X派生的

意思是一直追溯祖先如果没有别的分叉汇合到一点才叫菱形 ……

然后就没有什么大问题了,写就可以愉快的AC

 

#include<cstdio>
#include<cstring>
#include<string>
#include<map>
#include<bitset>
#include<iostream>
#define WR WinterRain
#define int long long
#define Class signed
using namespace std;
const int WR=1010;
int n,fa[WR];
int fth,tot;
map<string,int>mp;
bitset<WR>anc[WR],boom[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;
}
Class main(){
    freopen("class.in","r",stdin);
    freopen("class.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        bool wrong=false;
        fth=0;
        string nme,rlt,bin;
        cin>>nme;cin>>bin;cin>>rlt;//有个冒号恶心人
        while(rlt[0]!=';'){//输入祖先
            if(!mp[rlt]) wrong=true;//如果并没有这个祖先标记
            fa[++fth]=mp[rlt];//记录一下父亲们
            //你有几个爹啊————lcr
            cin>>rlt;//不能加break否则会重复输入
        }
        bitset<WR>tmp;//tmp的第i位表示i号节点是不是目前节点的父亲
        for(int j=1;j<=fth;j++){
            tmp[fa[j]]=1;
        }
        for(int j=1;j<=fth;j++){//如果爆炸了打标记
            if((tmp&boom[fa[j]]).any()) wrong=true;//如果不幸和父亲的雷点重合就炸
        }//这个boom在下面有讲
        if(wrong||mp[nme]){//之前声明过也要判
            printf("greska\n");
            continue;
        }
        printf("ok\n");
        mp[nme]=++tot;
        anc[tot][tot]=1;//加入新的映射
        for(int j=1;j<=fth;j++){
            anc[tot]|=anc[fa[j]];//anc表示ancestors,维护祖先
            //和上面的tmp差不多
        }
        for(int j=1;j<tot;j++){
            if(!anc[tot][j]&&(anc[j]&anc[tot]).any()){
                boom[tot][j]=1;
                //这里差不多是精髓了
                //如果j并非当前节点的祖先,而且当前节点和j节点共用一些祖先
                //那么可能形成菱形
                //这里我们要做标记,就像上面去使用
            }
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
AC代码

 

 

还剩两个小时看T4

当然是字符串……才怪

D. 2D

一看就非常不可做

 

 

 

 

 

 得出结论:不会(逃ε=ε=ε=┏(゜ロ゜;)┛

所以两个小时就这么过去了

写了些化学被谷老师diss

然后Eafoo居然AK,发现的确可以暴力

只能打出暴力,但因为数据太水暴力拓扑序能满分……

Update:Lyin加了两组Hack,过不去了(悲

 

#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#define WR WinterRain
#define int long long
#define KdGraph signed
using namespace std;
const int WR=1000100;
struct Edge{
    int pre,to;
}edge[WR<<1];
int n,m,M,N,B;
int sm,sn,sb;
int head[WR],cnt;
int deg[WR];
bool del[WR],vis[WR];
int in[WR];
int ans1,ans2=-1e18;
int tot,maxx;
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 add(int u,int v){
    edge[++cnt].pre=head[u];
    edge[cnt].to=v;
    head[u]=cnt;
}
void dfs(int u){
    sb+=in[u];
    sn++;
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(del[v]) continue;
        sm++;
        if(!vis[v]) dfs(v);
    }
}
void delet(int k){
    queue<int>q;
    for(int i=1;i<=n;i++){
        if(!del[i]&&deg[i]==k){
            del[i]=1;
            q.push(i);
            tot--;
        }
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=edge[i].pre){
            int v=edge[i].to;
            if(!del[v]){
                deg[v]--;
                if(deg[v]==k){
                    del[v]=1;
                    tot--;
                    q.push(v);
                }
            }
        }
    }
}
KdGraph main(){
    freopen("kdgraph.in","r",stdin);
    freopen("kdgraph.out","w",stdout);
    n=tot=read(),m=read();
    M=read(),N=read(),B=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read();
        add(u,v);add(v,u);
        deg[u]++,deg[v]++;
    }
    for(int i=1;i<=n;i++){
        in[i]=deg[i];
        if(!in[i]){
            tot--;
            del[i]=1;
        }
        maxx=max(deg[i],maxx);
    }
    for(int k=1;k<=maxx&&tot;k++){
        memset(vis,false,sizeof(vis));
        for(int i=1;i<=n;i++){
            if(!del[i]&&!vis[i]){
                sn=sm=sb=0;
                dfs(i);
                sb-=sm;
                sm>>=1;
                int res=sm*M-sn*N+sb*B;
                if(res>=ans2){
                    ans1=k;
                    ans2=res;
                }
            }
        }
        delet(k);
    }
    printf("%lld %lld\n",ans1,ans2);
    return 0;
}
91分代码

 

首先可以发现,如果不考虑连通性问题,(k + 1)-degree 一定是 k-degree 的子图

如果一个点属于 (k + 1)-degree 而不属于 k-degree,我们可以把这个点标记为 k + 1

所以我们可以考虑找到 k 最大的 k-degree,然后考虑不断的拓展这个子图,去得到 k 比较小 的 k-degree

显然每一次加入一批相同标记的点,可以拓展得到 k 减小的 k-degree

假设我们已经知道了当前图的 score ,我们考虑新增点/边会带来的 score 变化

我们首先给所有的节点一个 rank

• rank(v) > rank(u) 当且仅当

  – v 的标记 > u 的标记

  – v 的标记 = u 的标记且 v > u

• 这个部分可以用 bin-sort 完成

我们可以根据边两边点的 rank 对边分类,用 E(v, >) 表示 v 的边中另一个端点比 v rank 大 的边,其余的表示以此类推

于是,考虑新增一批点(标记相同的点):

对于每一个新加入的点 u:

• ∆n = 1

• ∆m = |E(u, >)| + 1 2 |E(u, =)|

• ∆b = |E(u, <)| − |E(u, >)|

于是从标记大的点到小的点依次加入计算 score,得到最大 score 的 k 即可

因为不断加入点,可能使得本来不连通的两个子图联通起来,这部分可以用并查集维护

时间复杂度 O(m + n)

题解是这么说的

于是我也就这么看了

正在研读,等读懂了再补(虽然大概率补不了了

Update:补一版smtwy的

//2D
//求最大子图的tarjan的强连通思想
//魔法少女运用并查集维护相对关系的思想 
//再有点带权并查集银河英雄传说的思想 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mx=1000000+1000;
int n,m; 
ll M,N,B;
int len;
int du[mx];//记录节点度数 
int head[mx*5];
int fa[mx];
ll ans=-999999999999999999,an1;
int q[mx];
int l,r;
struct Nodee{
    int belong;//这个点属于哪个k-degree图,belong存的是k值 它属于vector体系 
    int ns;//这个点所属联通块的点数 
    int bs;//边界边数 
    int ms;//边数
    //因为我们用并查集维护关系,所以除了father的值,其它的值都只是中转,并不是严格定义上的 “这个点所属联通块的点数” 
}o[mx];//下标是点的编号 
struct Node{
    int from;
    int to;
    int next;
}e[mx*5];//建图 
vector <int> num[mx];//储存k-degree的vector 
void Insert(int u,int v){
    e[++len].from=u;
    e[len].to=v;
    e[len].next=head[u];
    head[u]=len;
    du[u]++;
} //前向星建图并记录度数 
inline ll read(){ 
    ll x=0;
    ll w=1;
    char ch=getchar();
    while(isdigit(ch)==false){
        if(ch=='-')w=-1;
        ch=getchar();
    }
    while(isdigit(ch)==true){
        x =(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return x*w;
}

int find(int rt){
    if(fa[rt]==rt)return rt;
    return fa[rt]=find(fa[rt]); 
}
void merge(int x,int y){
    int xx=find(x);
    int yy=find(y);
    if(xx!=yy){//如果本次相连的两个点不属于同一个祖先 
        fa[yy]=xx;
        o[xx].ns+=o[yy].ns;//两个联通块的节点个数直接相加 
        o[xx].ms+=o[yy].ms+1;//边数相加并加1,加1是因为在并查集中,它们原本不相连,是我们此刻给它连了一条边
        //而它们之所以可以相连,是因为它们在原始图中是相连的,这条边一直存在 
        o[xx].bs+=o[yy].bs-2;//在并查集的定义中,此刻相连的这条边,原本在不相连时是两个连通块的边界边,它一连不是边界边,所以要减2 
        
    }
    else {//如果属于同一个祖先 
        o[xx].bs-=2;//那么两个点之间这条边,原本是双方各自的边界边,此刻成了相连边,所以减2 
        o[xx].ms++;//加的就是之间这条边 ,和上一行那个是同一条边 
    }
    //并查集这里为什么这么加一定要理解,这样我们就把题干要求的n,m,b通过带权并查集求出 
} 
void Solve(){
    n=read();m=read();
    M=read();N=read();B=read();
    //如果不写快读会被lyin卡掉,来自“挚友”的hack 
    l=1;
    r=0;
    for(int i=1;i<=m;++i){
        int x,y;
        x=read();
        y=read();
        Insert(x,y);
        Insert(y,x);
    }     
    for(int i=1;i<=n;++i){
        fa[i]=i;
        o[i].ns=1;//初始化每个点都是一个独立连通块 
        o[i].bs=du[i]; //刚开始,对于一个“独立”的点而言,它的度数就是边界边数(所有相连的边都是边界边) 
        //这里提醒一下,并查集记录的是联通块间的相对关系,vector存的是k-degree子图,e前向星是存储整张原始图,三者相互独立 
    } 
    int cnt=0;
    int maa=0; 
    for(int i=0;i<n;++i){//循环枚举k-degree的k值 
        for(int j=1;j<=n;++j){
            if(du[j]==i){
                if(i==0){//这个零度点的地方我处理的不太好,应该还可以再妙 
                    cnt++;
                }
                else {
                    q[++r]=j;//如果度值等于当前k值,扔进队列 
                }
            }
        }
        if(i==0)continue;
        while(l<=r){
            int u=q[l];
            l++;//这里如果用queue会被lyin卡掉 
            num[i].push_back(u);//把相同度数的点放进一个ve 
            o[u].belong=i;//同时记录属于关系,这样后面操作更方便一些
            //这两步跟强连通的存储差不多 
            cnt++; 
            for(int k=head[u];k;k=e[k].next){
                int v=e[k].to;
                du[v]--;//v点有一个相连的点u在k=i的范围内,u已经在i被归属,不会再对v点在更大的k-degree做贡献
                //所以直接减去 
                if(du[v]==i)q[++r]=v;//这里就是开队列的妙处,如果v减去之后度值也属于i了,就压进去,存起来 
            }
        }
        l=1,r=0;
        if(cnt==n){//假设有七个点,度的可能性为0到6,但是可能度为2的有3个,度为3的有4个
        //我们直接找全了7个点,就直接break就行,没有必要再循环下去
        //当然据lyin说当数据大了之后它总会跑到很大,后面多几次空循环也无所谓,所以其实没有什么用 
            maa=i;//记录所有的点,度数最大为几 
            break;
        } 
    }
    for(int i=maa;i>0;--i){//从k值最大的k-degree图开始,从里往外扩展 
        int nu=num[i].size();
        for(int j=0;j<nu;++j){//枚举这个k-degree图中的所有点 
            for(int k=head[num[i][j]];k;k=e[k].next){
                int v=e[k].to;
                if(i<o[v].belong){//如果当前i(k)值小于v的点k值
                //说明当前是小的外层,可以与内层相连
                //比如如果是红点k=3,那么这里一直不会执行,因为它的k是最大的,所以只会执行下面那个
                //把红点相互连起来。如果是绿点k=2,看最左边那个绿点,它可以连一个黄点k=1,没有任何用,跳过
                //它还可以连一个红点,k=3,那么就会执行这一步,扩展出去
                //这是用绿点向里连接,等于是又倒了一下。其实直接用红点向外连也行,而且更好理解
                //但是当时我考试就这么麻烦的写得
                //结果他们改题就都这么写hhhhhhhc,这才是最为高深的防伪标记 
                    merge(num[i][j],v);
                    continue;
                }
                if(i==o[v].belong && num[i][j]<v){//如果k值一样也要相连 
                //int u=num[i][j]. u<v,因为这是无向图,所以u,v可以互相找到,但是边只有一条
                //如果你重复合并值肯定就错了,所以这就保证了只会连一次,写成v<u也行
                //其实就相当于一个bool vis,但是这个题一个点可能会被连接多次,写vis不好处理 
                //而且这么写岂不是更帅,并起到一个强者检测器(这个词是给351238讲的时候想到的)的功能
                //因为这实际上就是一个图论找边不用vis的小优化,真正强的一眼就能看透
                //但是看不懂或者不愿意思考的肯定就很难了(这是351238说的,我没有别的意思hhhhhhc)
                    merge(num[i][j],v);
                }
            }
        }
        for(int j=0;j<nu;++j){
            //这里最后把这个k-degree子图的所有点再都循环一遍,找father,求解
            //一定要都循环一遍,因为实际数据可能会有多个联通块(它有的数据甚至有两万多个零度点)
            //不同联通块可能都有k值为2的点,它们在并查集肯定无关联,但是在ve中都存到一起了
            //就比如图的两块红区域,就要分开都算一遍,因为没有绿点加进来的时候它们肯定不连通 
            int fat=find(num[i][j]);
            ll op=M*o[fat].ms-N*o[fat].ns+B*o[fat].bs;
            if(ans<op){
                ans=op;
                an1=i;
            }
        }
    } 
    printf("%d %lld\n",an1,ans);//ok,开始调试环节 
}
int main(){
    freopen("kdgraph.in","r",stdin);
    freopen("kdgraph.out","w",stdout);
    Solve();
    return 0;
}
View Code

 

posted @ 2022-06-05 14:02  冬天丶的雨  阅读(132)  评论(12编辑  收藏  举报
Live2D