CSP模拟34

T2暴力也能过?

A. 斐波那契树

一眼看上去有点神奇,但是仔细想一想,其实也没有什么。

对于一棵生成树,树上有白边和黑边,我们会发现,对于白边的数量,会有一个最大值和最小值(这不废话吗),而且可以证明,在这个区间内的所有数,总有一种方案可以符合。

证明,可以先从下界,考虑用一个白边去换一个黑边,发现总可以换,如果不可以,那就到达了上界。

至于边界,把黑边边权设为 \(0\) ,白边边权设为 \(1\),上界即为最大生成树,下界即为最小生成树。因为 \(1e5\) 的斐波那契数就 \(25\) 个,复杂度很小。
所以可以检查每一个斐波那契数是否在范围内。

复杂度 \(O(Tm \log m)\)

点击查看代码
#include <iostream>
#include <cstdio>

const int MAXN=1e5+10;

using namespace std;

inline int read() {
    int f=1,x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') {
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return f*x;
}

int T,n,m,tot;
int fa[MAXN],f[30];
struct node {
    int u,v,col;
}a[MAXN];

void prework() {
    f[0]=1,f[1]=1;
    //f[++tot]=1;
    for(int i=2;;i++) {
        f[i]=f[i-1]+f[i-2];
        if(f[i]>MAXN*2) break;
        tot++;
    }
}

int find(int x) {
    if(fa[x]==x) return x;
    else return fa[x]=find(fa[x]);
}

void init() {
    for(int i=1;i<=n;i++) {
        fa[i]=i;
    }
}

int K1() {
    init();
    for(int i=1;i<=m;i++) {
        if(a[i].col==0) {
            int u=a[i].u,v=a[i].v;
            int fu=find(u) ,fv=find(v);
            if(fu!=fv) {
                fa[fu]=fv;
            }
        }
    }
    int sum=0;
    for(int i=1;i<=m;i++) {
        if(a[i].col==1) {
            int u=a[i].u,v=a[i].v;
            int fu=find(u),fv=find(v);
            if(fu!=fv) {
                fa[fu]=fv;
                sum++;
            }
        }
    }
    return sum;
}

int K2() {
    init();
    int sum=0;
    for(int i=1;i<=m;i++) {
        if(a[i].col==1) {
            int u=a[i].u,v=a[i].v;
            int fu=find(u) ,fv=find(v);
            if(fu!=fv) {
                fa[fu]=fv;
                sum++;
            }
        }
    }
    return sum;
}

int main() {

    T=read();

    prework();

    while(T--) {
        n=read() ,m=read();

        init();

        for(int i=1;i<=m;i++) {
            a[i].u=read();
            a[i].v=read();
            a[i].col=read();

            int fu=find(a[i].u) ,fv=find(a[i].v);
            if(fu!=fv) {
                fa[fu]=fv;
            }
        }

        int father=find(1),flag=1;
        for(int i=1;i<=n;i++) {
            if(father!=find(i)) {
                flag=0;
                cout<<"NO";
                putchar('\n');
                break;
            }
        }
        if(!flag) continue;

        flag=0;
        int l=K1() ,r=K2();
        for(int i=1;i<=tot;i++) {
            if(f[i]>=l && f[i]<=r) {
                flag=1;
                break;
            }
        }
        if(flag) cout<<"YES"; 
        else cout<<"NO";
        putchar('\n');

    }

    return 0;
}

B. 期末考试

赛时暴力能过???

可以先考虑暴力,即 \(4^{10}\) 枚举每种答案,再 \(10n\) 判断是否可行。

然后考虑正解,其实是在暴力的基础上的.思路类似于一种拼接的方法。

可以先枚举前 \(5\) 个题的答案,然后想像暴力一样枚举,计算每种答案对于每个字符串,可以对几个,把这几个数用哈希压一下,把这个哈希值累加一下。

例如 我枚举的答案是\(AAAAA\),给定的几个字符串的前5个字符为 \(ABCBA\) ,\(BABDC\) ,
那我就把 \(2,1\) 压成哈希,在把其的桶加一。

然后再枚举后 \(5\) 个题的答案,像上面一样计算每个字符串又对几个,就可以算出前 \(5\) 个字符需要对几个,设为 \(res\) ,求出哈希,\(ans\) 直接累加就可以了。

复杂度为 \(O(2^{11}\sum n )\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>

#define int long long

const int MAXN=2e4+10;
const int M=1e7+10;
const int P=131;

using namespace std;

inline int read() {
    int f=1,x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') {
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return f*x;
}

int T,n,ans;
int val[MAXN],pos[11];
string s[MAXN];      

map <unsigned long long,int> ha;

void dfs1(int now) {
    if(now==6) { 
        unsigned long long h=0;   
        for(int i=1;i<=n;i++) {
            int sum=0;
            for(int j=0;j<5;j++) {
                int ch=s[i][j]-'A'+1;
                if(ch==pos[j+1]) {
                    sum++;
                }
            }
            if(sum>val[i]) return;
            h=h*P+sum;
        }
        ha[h]++;
        return;
    }
    for(int i=1;i<=4;i++) {
        pos[now]=i;
        dfs1(now+1);
    }
}

void dfs2(int now) {
    if(now==6) {
        int h=0;
        for(int i=1;i<=n;i++) {
            int sum=0;
            for(int j=5;j<10;j++) {
                int ch=s[i][j]-'A'+1;
                if(ch==pos[j-4]) {
                    sum++;
                }
            }
            int res=val[i]-sum;
            if(res<0 || res>5) return;
            h=h*P+res;
        }
        ans+=ha[h];
        return;
    }
    for(int i=1;i<=4;i++) {
        pos[now]=i;
        dfs2(now+1);
    }
}

void dfs3(int now) {
    if(now==6) { 
        int h=0;   
        for(int i=1;i<=n;i++) {
            int sum=0;
            for(int j=0;j<5;j++) {
                int ch=s[i][j]-'A'+1;
                if(ch==pos[j+1]) {
                    sum++;
                }
            }
            h=h*P+sum;
        }
        ha[h]=0;
        return;
    }
    for(int i=1;i<=4;i++) {
        pos[now]=i;
        dfs3(now+1);
    }
}

signed main() {

    T=read();
    while(T--) {
        n=read();
        for(int i=1;i<=n;i++) {
            cin>>s[i];
            val[i]=read()/10;
        }

        ans=0;
        dfs1(1);
        dfs2(1);
        ha.clear();
        cout<<ans; putchar('\n');
    }

    return 0;
}

C. 麻烦的工作

感觉很厉害的一道题,其中转化的方式之前见过。

首先考虑暴力贪心,我们想让每个人做尽可能多的任务,这样任务才可能做完,所以一个贪心策略就是,按任务所需人数从大到小排序,先安排需要人数多的任务,把任务安排给剩余可做任务多的社畜人,这样显然更优。

在贪心的基础上考虑如何优化,考虑这个贪心的本质。对于前 \(k\) 个任务,每个社畜 \(i\) 最多被安排 \(\min(k,b_i)\) 个任务,总共为 \(\sum_{i=1}^m \min(b_i,k)\) ,如果想要把所有的工作完成,要有 \(\sum_{i=1}^m \min(b_i,k) \geqslant \sum_{i=1}^k a_i\).

即:

\[\forall k \in [1,n], \sum_{i=1}^m \min(b_i,k) \geqslant \sum_{i=1}^k a_i \]

这个式子右面是前缀和的形式,左边不好维护,考虑转化。

建立一个平面直角坐标系,\(x\)轴 为社畜的编号 \(i\)\(y\)轴为 \(b_i\) ,每个社畜的一个单位的工作可以当做一个点,每个 \(k\) 看做平行于 \(x\)轴的一条直线,左边的式子即为在直线 \(y=k\) 上和直线下面的点的总个数,
只不过左边式子是枚举 \(i\) 来求的,考虑枚举 \(k\) 来求,设 \(val_k\) 表示一共有多少个人的 \(b_i\) 大于等于 \(k\),那么左边的式子可以写成 \(\sum_{i=1}^k val_i\),再加上前缀和改写。

即:

\[\forall k \in [1,n],sval_k - sa_k \geqslant 0 \]

因为需要修改,所以用线段树维护 \(sval_k - sa_k\)的最小值,与 \(0\) 比对就可以了。

对于修改 \(b_i\) ,\(b_i+1\) 会使 \(val_{b_i+1}+1\) ,所以对于前缀和直接区间覆盖就可以了,\(b_i-1\)同理。

对于修改 \(a_i\) ,我们要使整个序列是单调不升的,我们不希望每次修改都再排一次序,那么我们对于 修改 \(a_i\) ,用二分找到和 \(a_i\) 相同的一段数的头和尾,直接修改头或者尾,这样就不用重新排序了。注意修改时直接不要在排完序的序列里找,要在原序列里找到要修改的值,再用二分在排完序的序列里找。

复杂度 \(O(q\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

#define rint register int 

const int MAXN=3e5+10;

using namespace std;

inline int read() {
    int f=1,x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') {
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return f*x;
}

int n,m,q;
int a[MAXN],o[MAXN],b[MAXN],sum[MAXN];
int val[MAXN],sval[MAXN];

struct Seg_Tree{

    struct Tree {
        int mi,lazy;
    }t[MAXN<<2];

    inline void push_up(int k) {
        t[k].mi=min(t[k<<1].mi,t[k<<1|1].mi);
    }

    inline void push_down(int k) {
        if(t[k].lazy) {
            t[k<<1].mi+=t[k].lazy ,t[k<<1].lazy+=t[k].lazy;
            t[k<<1|1].mi+=t[k].lazy ,t[k<<1|1].lazy+=t[k].lazy;
            t[k].lazy=0;
        }
    }

    void build(int k,int l,int r) {
        if(l==r) {
            t[k].mi=sval[l]-sum[l];
            return;
        } 
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        push_up(k);
    }

    void up_date(int k,int l,int r,int L,int R,int val) {
        if(L<=l && r<=R) {
            t[k].mi+=val;
            t[k].lazy+=val;
            return;
        }
        int mid=(l+r)>>1;
        push_down(k);
        if(L<=mid) up_date(k<<1,l,mid,L,R,val);
        if(R>mid) up_date(k<<1|1,mid+1,r,L,R,val);
        push_up(k);
    }
}T;

bool cmp(int x,int y) {
    return x>y;
}

inline int get1(int x) {
    int l=1,r=n,res=0;
    while(l<=r) {
        int mid=(l+r)>>1;
        if(a[mid]==x) {
            res=mid;
            r=mid-1;
        }
        else if(a[mid]>x) {
            l=mid+1;
        }
        else {
            r=mid-1;
        }
    }
    return res;
}                                           

inline int get2(int x) {
    int l=1,r=n,res=0;
    while(l<=r) {
        int mid=(l+r)>>1;
        if(a[mid]==x) {
            res=mid;
            l=mid+1;
        }
        else if(a[mid]>x) {
            l=mid+1;
        }
        else {
            r=mid-1;
        }
    }
    return res;
}

inline void work() {
    int mi=T.t[1].mi;
    if(mi<0) cout<<"0";
    else cout<<"1";
    putchar('\n');
}

int main() {

    n=read() ,m=read();
    for(rint i=1;i<=n;++i) {
        o[i]=read();
        a[i]=o[i];
    }
    sort(a+1,a+n+1,cmp);
    for(rint i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];

    for(rint i=1;i<=m;++i) {
        b[i]=read();
        val[b[i]]++;
    }
    for(rint i=n;i>=1;--i)  val[i]+=val[i+1];
    for(rint i=1;i<=n;++i) sval[i]=sval[i-1]+val[i];

    T.build(1,1,n);

    q=read();
    for(rint Q=1;Q<=q;Q++) {
        int opt=read(),pos=read();
        if(opt==1) {
            int p=get1(o[pos]);
            a[p]++ ,o[pos]++;
            T.up_date(1,1,n,p,n,-1);
        }
        if(opt==2) {
            int p=get2(o[pos]);
            a[p]-- ,o[pos]--;
            T.up_date(1,1,n,p,n,1); 
        }
        if(opt==3) {
            if(b[pos]<n) T.up_date(1,1,n,b[pos]+1,n,1);
            b[pos]++;
        }
        if(opt==4) {
            if(b[pos]<=n) T.up_date(1,1,n,b[pos],n,-1);
            b[pos]--;
        }
        work();
    }

    return 0;
}
posted @ 2023-09-11 06:44  Trmp  阅读(10)  评论(0编辑  收藏  举报
-->