CSP模拟10

现在有这样一种感觉:是在留下永远不会在有人看的遗产。

T1

正解并查集,直接把每次给你的 \(x,y\) 用并查集合并一下(没有 \(y\) 就把 \(x\)\(0\) 合并一下)并加入答案,如果已经在一个集合里就不要加入答案就行了。下面这个不是我写的。

int main()
{
    H=read(); L=read();
    for(int i=0;i<=L;i++) fa[i]=i;
    for(int i=1;i<=H;i++)
    {
        int opt=read();
        if(opt==1)
        {
            int x=read();
            if(find(x)==find(0)) continue;
            ans[++pol]=i; merge(x,0);
        }
        else
        {
            int x=read(),y=read();
            if(find(x)==find(y)) continue;
            ans[++pol]=i; merge(x,y);
        }
    }
    printf("%d %d\n",Qun(2,pol),pol);
    for(int i=1;i<=pol;i++) printf("%d ",ans[i]);
    return 0;
}

考场随手拿 \(\text{priority_queue}\) 手动模拟了一下过了。joke3579好像也是暴力模拟过的。不过他用的vector。
跑的很慢。
订正:重评以后被卡死了。



/*线性基裸题 考虑怎么插入这么大的一个数*/
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int mod=1000000007;
int n,m,a[500010];
vector<int>p[500010];
bool v[500010];
priority_queue<int>q;
void ins(int id){
    while(!q.empty()){
        int x=q.top(),cnt=0;
        while(!q.empty()&&q.top()==x){
            q.pop();cnt++;
        }
        if(cnt&1){
            if(p[x].size()==0){
                p[x].push_back(x);
                while(!q.empty()){
                    int y=q.top(),ret=0;
                    while(!q.empty()&&q.top()==y){
                        q.pop();ret++;
                    }
                    if(ret&1)p[x].push_back(y);
                }
                v[id]=true;
            }
            else{
                for(int i=0;i<p[x].size();i++){
                    if(p[x][i]!=x)q.push(p[x][i]);
                }
            }
        }
    }
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*a*ans%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int od,x,y;scanf("%d",&od);
        if(od==1){
            scanf("%d",&x);q.push(x);
        }
        else{
            scanf("%d%d",&x,&y);q.push(x);q.push(y);
        }
        ins(i);
    }
    int ans=0;
    for(int i=1;i<=m;i++)if(p[i].size())ans++;
    printf("%d %d\n",qpow(2,ans),ans);
    for(int i=1;i<=n;i++)if(v[i])printf("%d ",i);
}

T2

考场暴力写挂了光荣保龄。
我们随便找一个不是叶子的节点做根,然后考虑哪些情况不合法。

  1. 子树内部的节点个数比当前节点还少,那么子树消完了当前节点也消不完,不合法。
  2. 当前节点的2倍比子树还少,那么由于消的时候只有子树之间消(子树-2,当前节点-1)或和外面的消(子树-1,当前节点-1),那么当前节点消完了子树也消不完,不合法。
  3. 子树中最大的一个比当前节点还大,显然消不完,不合法。
  4. 消完之后根节点不为 \(0\) ,不合法。
    对每个节点判一下就行了。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
struct node{
    int v,next;
}edge[200010];
int n,t,rt,a[100010],head[100010],d[100010],val[100010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
void dfs1(int x,int f){
    if(d[x]==1){
        val[x]=a[x];return;
    }
    val[x]=a[x]<<1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs1(edge[i].v,x);
            val[x]-=val[edge[i].v];
        }
    }
}
void dfs2(int x,int f){
    if(d[x]==1)return;
    if(val[x]>a[x]){
        printf("NO");exit(0);
    }
    int ans=0;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            if(val[edge[i].v]>a[x]-val[x])ans+=val[edge[i].v]+val[x]-a[x];
        }
    }
    if(ans>val[x]){
        printf("NO");exit(0);
    }
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f)dfs2(edge[i].v,x);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
        d[u]++;d[v]++;
    }
    if(n==2){printf("%s",a[1]==a[2]?"YES":"NO");return 0;}
    for(int i=1;i<=n;i++)if(d[i]!=1){
        rt=i;break;
    }
    dfs1(rt,0);
    if(val[rt]){
        printf("NO");return 0;
    }
    dfs2(rt,0);
    printf("YES");
    return 0;
}

T3

提供一种不太一样但是个人觉得比较好理解的思路。(以下省略向上取整)
我们排序之后从大到小考虑。由于我们最后的答案肯定是一个区间一个区间的,所以我们只需要知道每个区间的上下界。
考虑现在我们扫到一个点的上界是 \(r\) ,那么我们如果扫到下一个点 \(x\) 并试图扩展上界,那么扩展到的上界应当是 \(r+x\) ,因为 \(x\le r\) ,那么 \(\frac {r+x}2\le r\) ,可以扩展到。也就是说,我们每个点的上界就是这个点及后面的所有点的和,直接从小到大排序然后跑前缀和就可以。
仍然从大到小考虑,现在我们的下界是 \(l\) 。我们扫到一个点 \(x\) ,由于它比之前的都要小,所以新的下界是 \(\frac x2\) 。但是这个点的上界是 \(x\) ,而原来的下界可能扩展不到新的上界,于是就出现了一个新的连续段。
扫一遍统计所有连续段即可。代码总共20行。

#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
int n,ans,a[100010],down[100010],sum[100010],pos[100010];
signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    sort(a+1,a+n+1);sum[0]=-1;
    for(int i=1;i<=n;i++){
        sum[i]=(i==1)?a[1]:(sum[i-1]+a[i]);
        down[i]=(a[i]+1)/2;pos[i]=i;
        if(down[i]<=sum[i-1]+1)down[i]=down[i-1],pos[i]=pos[i-1];
    }
    for(int i=n;i>=1;i--){
        ans+=sum[i]-down[i]+1;
        i=pos[i];
    }
    printf("%lld",ans);
}

T4

原题。不会回滚莫队。

posted @ 2022-09-24 15:06  gtm1514  阅读(35)  评论(0编辑  收藏  举报