• 9.4正睿CSP七连测day2


    辛亏我没考,不然铁定掉 rating。

    T1

    T1 比较水,但是坑点比较多。挂了两次,第一次是没有处理前导 (0),第二次是没有处理后缀非数字字符。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N number
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    string s;
    bool op=1;
    ll a[5],tail;
    
    int main(){
        cin>>s;int len=s.length();
        for(int i=0,j;i<=len-1;){
            if(s[i]<'0'||s[i]>'9'){
                if(tail==4) op=0;
                if(s[i]!='.') op=0;
                if(i>0&&(s[i-1]<'0'||s[i-1]>'9')) op=0;
                i++;
                continue;
            }
            tail++;
            bool pd=1;
            for(j=i;j<=len-1;j++){
                if(s[j]=='0'&&pd) op=0; 
                if(s[j]!='0') pd=0;
                if(s[j]>='0'&&s[j]<='9') a[tail]=1ll*a[tail]*10+s[j]-'0';
                else break;
            }
            i=j;
        }
        for(int i=1;i<=tail;i++) if(a[i]>255){a[i]=255;op=0;}
        if(op){printf("YES
    ");return 0;}
        printf("NO
    ");
        for(int i=1;i<=tail;i++){
            printf("%lld",a[i]);if(i!=tail) putchar('.');
        }
        return 0;
    }
    

    T2

    也比较水,只需要看前导 P 和 A,然后记录一下前导 P 的数量看能不能消即可。挂了一次,挂在前导 P 不应该清零。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 10010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int cnt=0,ans=0,tot;
    char s[N];
    
    int main(){
        scanf("%s",s);int len=strlen(s);
        for(int i=0;i<=len-1;i++){
            
            if(s[i]=='A'){cnt++;}
            if(s[i]=='P'){
                if(cnt){cnt--;ans++;}
                else{
                    tot++;
                    if(tot==2){tot=0;ans++;}
                }
            }
        }
        printf("%d
    ",len-ans*2);
    }
    

    T3

    T3 主要难度在于菱形的判断,稍加分析之后不难看出,我们记 (bit) 为若干 bitset,其中如果有一位为 (1) 表示两个点他们不互相派生且存在一个点可以派生出这两个点。

    那么判断可不可以的时候我们只需要枚举直接继承点,构造一个 bitset,全部交一遍,看是否有交集就可以判断出是否可行。

    我们考虑如何维护 (bit),我们在记一个 (f),同样也是一个 bitset,不过这个表示的是能够到达某个点的所有点所在集合,注意我们认为自身可以到达自身。显然我们可以把新加的点的 (f) 数组处理出来。然后我们枚举所有点,如果这个点到达不了当前点,并且他们的 (f) 值有交集,那么我们就相应更改 (bit)。复杂度为 (O(frac{n^3}{w}))

    不过这个题还可以优化到 (O(n^2log n)) 这里简单的提一句。

    我们假设当前的这个语句为 (k:p_1,p_2,...p_k;),我们把后面这些 (p) 按照声明顺序从大到小排序。显然只有声明顺序小的才能继承出声明顺序大的。 我们一次处理每一个 (p),维护一个 (R) 表示能到达 (k) 的节点集合。对于 (p_i),如果 (R) 里面这个位置已经为 (1),那么我们就不管,因为能够到达 (p_i) 也肯定已经在 (R) 中了,否则,我们把 (R) 与能够到达 (p_i) 的集合合并,如果有交,就说明一定出现了菱形。

    我们看一下时间复杂度,这个 (R) 一共有 (n) 个位置,所以复杂度均摊下来是 (O(n)),一共做 (n) 次,所以复杂度为 (O(n^2))。时间复杂度瓶颈在排序。如果用桶排,理论更优,但是常数较大,实际时间不如直接 sort。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 1010
    #define M number
    using namespace std;
    // #define fre
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int n,tail=0;
    map<string,int> Map;
    bitset<1010> bit[N],f[N];
    string s[N],ss;
    
    int main(){
        #ifdef fre
            freopen("my.in","r",stdin);
            freopen("my.out","w",stdout);
        #endif
        read(n);
        for(int i=1;i<=n;i++){
            cin>>ss;int j=0;cin>>s[j];
            while(s[j][0]!=';') cin>>s[++j];j--;
            bool op=1;
            if(Map.find(ss)!=Map.end()){printf("greska
    ");continue;}
            for(int k=1;k<=j;k++) if(Map.find(s[k])==Map.end()){op=0;break;}
            if(!op){printf("greska
    ");continue;}
            bitset<1010> sum;sum.reset();
            for(int k=1;k<=j;k++) sum[Map[s[k]]]=1;
            for(int k=1;k<=j;k++) if((sum&bit[Map[s[k]]]).any()){op=0;break;}
            if(!op){printf("greska
    ");continue;}
            printf("ok
    ");++tail;Map[ss]=tail;f[tail][tail]=1;
            for(int k=1;k<=j;k++) f[tail]|=f[Map[s[k]]];
            for(int k=1;k<=tail-1;k++){
                if(f[tail][k]==0&&(f[k]&f[tail]).any()) bit[tail][k]=1;
            }
        }
    }
    

    T4

    毒瘤题目。谢老板nb。

    首先不难发现,(k-degree) 子图一定包含于 ((k-1)-degree) 子图中,并且子图的数量本来就不多,这启示我们这道题应该是需要大力维护出这些子图的信息出来。

    那么首先我们需要维护出每一个点最多能存在于那个子图中,即把 (k) 最大化的 (k-degree) 子图中。假设我们已经求出来了,并且存到了 (vis) 这个数组里面。我们下面称这个值为这个点的标记。

    我们考虑从标记最大的点开始,一层一层扩张,每次需要维护集合合并,我们考虑用并查集维护。

    那么这个题分两个步骤,我们首先来看第一个步骤怎么做。

    我们首先桶排所有的点,按照他们的度数,然后我们从小到大枚举每一个点,考察每一条边,如果对点的当前度数大于你的度数,你就把它的度数减 (1),然后维护这个序列的有序性,维护方法就是维护值得分界线,这段代码如下:

        inline void GetVis(){
            for(int i=1;i<=n;i++) r[i].id=i,r[i].vis=D[i];
            for(int i=1;i<=n;i++) c[r[i].vis]++;
            for(int i=1;i<=n;i++) c[i]+=c[i-1];
            for(int i=n;i>=1;i--) Rank[i]=c[r[i].vis]--;
            for(int i=1;i<=n;i++) a[Rank[i]]=r[i];
            for(int i=1;i<=n-1;i++) if(a[i].vis!=a[i+1].vis) line[a[i].vis]=i;
            for(int i=1;i<=n;i++){
                for(int x=head[a[i].id];x;x=li[x].next){
                    int to=li[x].to;
                    if(a[Rank[to]].vis<=a[i].vis) continue;
                    a[Rank[to]].vis--;
                    int posi=line[a[Rank[to]].vis]+1;
                    line[a[Rank[to]].vis]++;
                    int id=a[posi].id;
                    swap(a[Rank[to]],a[Rank[id]]);
                    swap(Rank[to],Rank[id]);
                }
            }
            for(int i=1;i<=n;i++) vis[a[i].id]=a[i].vis;
            #ifdef debug
                printf("here
    vis: ");Print(vis);
            #endif
        }
    

    我们考虑正确性。我们先考虑一种显然正确的方法。我们令 (k) 为当前最小度数,然后我们删除全部度数小于等于 (k) 的点,需要及时修改一些点的度数,直到剩下的图全是度数大于 (k) 的点。这些被删除的点的标记都为 (k),我们考虑进一步增大 (k)。一直到整张图被删完。但是这种做法其实复杂度不对,原因就是我们可能会保留一些点较长时间,复杂度上界是根号的,但是到不了这个级别,但也绝对不是线性。我们考虑线性怎么做。

    显然我们删除度数为 (k) 的点会改变一些点的度数,如果这些点原本的度数就已经小于等于 (k)(有可能被其他点删除导致小于 (k)),那么其实不管怎样这个点都将会在这一轮中删除。有影响的是那些大于 (k) 的度数,他们可能会被先删。我们一开始介绍的那种方法其实就是模拟了这一点,假设当前我们枚举的位置的度数为 (k),那么这个 (k) 一定是其标记,因为只有小于 (k) 的那些才能够更新他们。我们检查其每一条出边,我们可以让一些度数大的提早被删除。做完后剩下的度数就是标记。

    有了标记之后我们怎么做?我们考虑把所有点按照标记排序,我们从大标记开始处理。考虑用并查集进行连通块之间的合并,我们记录每个连通块的点内度数之和,点数,边数之和。其中,前两个信息都是可以直接预处理合并的,我们考虑这个边数之和如何合并。

    我们首先把所有点按照标记合并,我们枚举每个点,然后枚举每一条出边,如果对点在该点左侧,我们不管,这样可以保证每条边只被遍历一次,我们考虑该点与对点所在连通块,若不在同一个连通块,我们把连通块合并,边数当然也合并,然后我们把这条边的影响加上。

    考虑正确性,其实是比较显然的,因为你每条边只被遍历一次,每次遍历会给在相应的连通块上进行贡献。

    每次枚举到分界点后我们进行修改答案,看能不能更优,因为可能不连通,我们需要枚举所有点。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    // #define int long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 5000100
    #define M number
    using namespace std;
    // #define fre
    // #define debug
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    template<typename T> inline T Min(T a,T b){return a<b?a:b;}
    template<typename T> inline T Max(T a,T b){return a<b?b:a;}
    
    int ns[N],ms[N],ds[N],maxk,mink;
    
    //最终答案。
    ll ans=-INF,ansk;
    
    int fa[N];
    inline int Find(int x){
        return x==fa[x]?x:Find(fa[x]);
    }
    
    struct edge{
        int to,next;
        inline void intt(int to_,int ne_){to=to_;next=ne_;}
    }li[N<<1];
    int head[N],tail,n,m,mm,nn,bb,d[N],D[N];
    
    int vis[N];//打标记,有没有被删除,如果有,是在 k 等于几的时候被删除的。
    int c[N],Rank[N],line[N],linetail;
    
    struct rode{
        int vis,id;
    }r[N],a[N];
    
    struct Graph{
        //Init the graph
        inline void Init(){
            read(n);read(m);read(mm);read(nn);read(bb);
            for(int i=1;i<=m;i++){int from,to;read(from);read(to);Add(from,to);Add(to,from);}
            #ifdef debug
                printf("here
    ");
            #endif
        }
        //add an edge
        inline void Add(int from,int to){
            li[++tail].intt(to,head[from]);
            head[from]=tail;D[to]++;
        }
        //这个函数会求的 vis 数组,是每个位置的标记。
        inline void GetVis(){
            for(int i=1;i<=n;i++) r[i].id=i,r[i].vis=D[i];
            for(int i=1;i<=n;i++) c[r[i].vis]++;
            for(int i=1;i<=n;i++) c[i]+=c[i-1];
            for(int i=n;i>=1;i--) Rank[i]=c[r[i].vis]--;
            for(int i=1;i<=n;i++) a[Rank[i]]=r[i];
            for(int i=1;i<=n-1;i++) if(a[i].vis!=a[i+1].vis) line[a[i].vis]=i;
            for(int i=1;i<=n;i++){
                for(int x=head[a[i].id];x;x=li[x].next){
                    int to=li[x].to;
                    if(a[Rank[to]].vis<=a[i].vis) continue;
                    a[Rank[to]].vis--;
                    int posi=line[a[Rank[to]].vis]+1;
                    line[a[Rank[to]].vis]++;
                    int id=a[posi].id;
                    swap(a[Rank[to]],a[Rank[id]]);
                    swap(Rank[to],Rank[id]);
                }
            }
            for(int i=1;i<=n;i++) vis[a[i].id]=a[i].vis;
            #ifdef debug
                printf("here
    vis: ");Print(vis);
            #endif
        }
        inline void Print(int *a){
            for(int i=1;i<=n;i++) printf("%lld ",a[i]);puts("");
        }
        inline void PreWork(){
            fill(c+1,c+n+1,0);fill(Rank+1,Rank+n+1,0);
            for(int i=1;i<=n;i++) c[vis[i]]++;
            for(int i=1;i<=n;i++) c[i]+=c[i-1];
            for(int i=n;i>=1;i--) Rank[i]=c[vis[i]]--;
            for(int i=1;i<=n;i++) c[Rank[i]]=i;
            for(int i=1;i<=n;i++) ns[i]=1,ds[i]=D[i];
        }
        inline void Solve(){
            for(int i=1;i<=n;i++) fa[i]=i;
            for(int i=n;i>=0;i--){
                int now=c[i];
                int fai=Find(now);
                for(int x=head[now];x;x=li[x].next){
                    int to=li[x].to;
                    if(Rank[to]<Rank[now]) continue;
                    int fato=Find(to);
                    if(fai!=fato){
                        if(ns[fai]<=ns[fato]) swap(fato,fai);
                        fa[fato]=fai;ns[fai]+=ns[fato];ds[fai]+=ds[fato];ms[fai]+=ms[fato];
                    }
                    ms[fai]++;
                }
                if(vis[now]!=vis[c[i-1]]){
                    for(int j=i;j<=n&&vis[c[j]]==vis[now];j++){
                        int nowfa=Find(c[j]);
                        long long val=1ll*mm*ms[nowfa]-1ll*nn*ns[nowfa]+1ll*bb*(ds[nowfa]-(ms[nowfa]<<1));
                        if(val>ans){ans=val;ansk=vis[now];}
                    }
                }
            }
        }
    }g;
    
    signed main(){
        #ifdef fre
            freopen("my.in","r",stdin);
            freopen("my.out","w",stdout);
        #endif
        g.Init();g.GetVis();g.PreWork();g.Solve();
        printf("%lld %lld
    ",ansk,ans);
        return 0;
    }
    
  • 相关阅读:
    CLBZDQ
    CF1559D 题解
    DP 的凸优化
    正睿暑期集训7B
    基于 TiSpark 的海量数据批量处理技术
    PowerDesigner16.5下载和安装教程
    使用TiDB MPP
    使用 TiDB 构建实时应用
    oracle转mysql数据库
    kafka-jdbc-connector-sink实现kafka中的数据同步到mysql
  • 原文地址:https://www.cnblogs.com/TianMeng-hyl/p/15235860.html
Copyright © 2020-2023  润新知