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;
}
原文地址:https://www.cnblogs.com/TianMeng-hyl/p/15235860.html