【补题】2022年中国大学生程序设计竞赛女生专场

比赛链接

2022年中国大学生程序设计竞赛女生专场

A.减肥计划

签到题

暴力模拟,如果模拟到最重的人到队首还没有人连续获胜\(k\)轮,那么答案显然就是最重的人。

方便起见可以用双端队列模拟。

时间复杂度\(\Theta(n)\)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#define maxn 1000010
using namespace std;
struct dat{
    int val,pos;
    dat(){}
    dat(int t1,int t2){val=t1,pos=t2;}
} a[maxn];
deque<dat> q;
int cnt[maxn];
int main(){
    int i,j,n,k,flag=0,ok=0,p,ans;
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;++i){
        scanf("%d",&a[i].val);
        a[i].pos=i;
        q.push_back(a[i]);
        if(!p||a[i].val>a[p].val) p=i;
    }
    for(i=1;i<=n;++i){
        dat x=q.front();
        q.pop_front();
        dat y=q.front();
        q.pop_front();
        if(x.val>=y.val){
            cnt[x.pos]++,cnt[y.pos]=0;
            q.push_front(x);
            q.push_back(y);
        }
        else{
            cnt[y.pos]++,cnt[x.pos]=0;
            q.push_front(y);
            q.push_back(x);
        }
        if(cnt[x.pos]>=k){
            ans=x.pos;
            ok=1;
            break;
        }
        if(cnt[y.pos]>=k){
            ans=y.pos;
            ok=1;
            break;
        }
    }
    if(!ok) ans=p;
    printf("%d",ans);
    // system("pause");
    return 0;
}

C.测量学

签到题

首先如果\(\theta>\pi\),令\(\theta=2\pi-\theta\)

写出路程和\(r\)之间的关系式\(dis=2*(R-r)+theta*r\),发现是关于\(r\)的一次函数。然后就做完了。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define db double
#define maxn 100010
using namespace std;
db r[maxn];
const db pi=acos(-1);
int main(){
    int i,j,n,m;
    db R,theta,ans;
    scanf("%d%lf%lf",&n,&R,&theta);
    if(theta>pi) theta=2*pi-theta;
    for(i=1;i<=n;++i) scanf("%lf",&r[i]);
    sort(r+1,r+n+1);
    if(theta>2) ans=2*(R-r[1])+theta*r[1];
    else ans=theta*R;
    printf("%lf",ans);
    // system("pause");
    return 0;
}

E. 睡觉

简单题

首先根据\(d\)预处理一下\(a\)数组,得到一个只含\(\pm 1\)的数组\(b\),然后\(d\)就没用了。

判下最简单的情况,如果\(\sum_{i=1}^n b_i<0\),那么显然可以,输出YES。

如果\(\sum_{i=1}^n b_i>0\),倍长数组,模拟过程,如果找到连续的清醒值\(\leq k\)的片段就输出\(YES\),否则输出\(NO\)

比较麻烦的是\(\sum_{i=1}^n b_i==0\)的情况,假如模拟两轮后未找到连续的清醒值\(\leq k\)的片段,并不一定代表不可行(考虑\(t>>n\)的情况)。

所以特判,只要这两轮中一直满足清醒值\(\leq k\),那么就可行。

代码

点击查看代码
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define maxn 100010
using namespace std;
int a[maxn],b[maxn];
int solve(){
    int i,j,n,k,x,t,d,sum=0,s=0;
    scanf("%d%d%d%d%d",&x,&t,&k,&n,&d);
    for(i=1;i<=n;++i) scanf("%d",&a[i]);
    for(i=1;i<=n;++i) b[i]=a[i]>d?1:-1;
    for(i=1;i<=n;++i) sum+=b[i];
    if(sum<0) return 1;
    for(i=1;i<=2*n;++i){
        x+=b[(i-1)%n+1];
        if(x<=k) s++;
        else s=0;
        if(s>=t) return 1;
    }
    if(sum==0&&x==k){
        for(i=1;i<=2*n;++i){
            x+=b[(i-1)%n+1];
            if(x>k) return 0;
        }
        return 1;
    }
    return 0;
}
int main(){
    int i,j,n,m,T;
    scanf("%d",&T);
    while(T--){
        if(solve()) printf("YES\n");
        else printf("NO\n");
    }
    // system("pause");
    return 0;
}

G. 排队打卡

简单题

按时间顺序处理事件即可

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define maxm 500010
#define ll long long
using namespace std;
struct event{
    int t;
    ll x;
    bool operator <(const event z) const{
        return t<z.t;
    }
    event(){}
    event(int t1){t=t1;}
} a[maxm];
ll p[maxm];
int main(){
    int t,m,i,j,anst=0;
    ll n,k,sum=0,ans=-1;
    scanf("%d%lld",&t,&n);
    scanf("%d%lld",&m,&k);
    for(i=1;i<=m;++i){
        scanf("%d%lld",&a[i].t,&a[i].x);
    }
    sort(a+1,a+m+1);
    int g=upper_bound(a+1,a+m+1,event(t))-a-1;
    for(i=1;i<=m;++i){
        p[i]=max(p[i-1]-k*(a[i].t-a[i-1].t),0ll)+a[i].x;
    }
    if(max(p[g]-k*(t-a[g].t),0ll)!=n){
        printf("Wrong Record");
        return 0;
    }
    for(i=g+1;i<=m;++i){
        ll tmp=p[i]/k+1;
        if(tmp<=ans||ans<0) ans=tmp,anst=a[i].t;
    }
    printf("%d %lld",anst,ans);
    // system("pause");
    return 0;
}

H. 提瓦特之旅

题出的很好,但《原神》是由米哈游自主研发的一款全新开放世界冒险游戏。游戏发生在一个被称作「提瓦特」的幻想世界,在这里,被神选中的人将被授予「神之眼」,导引元素之力。你将扮演一位名为「旅行者」的神秘角色 在自由的旅行中邂逅性格各异、能力独特的同伴们,和他们一起击败强敌,找回失散的亲人——同时,逐步发掘「原神」的真相。

简单题

无非是带状态的最短路,分层图解决即可(其实并不需要真的把新图建出来)。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<queue>
#include<algorithm>
#define maxn 501
#define maxm 100010
#define inf 1ll<<56
#define ll long long
using namespace std;
ll dis[maxn][maxn];
int book[maxn][maxn];
int fst[maxn],nxt[maxm<<1],to[maxm<<1],cnt=0;
ll w[maxm<<1],b[maxn];
void add(int x,int y,ll z){
    w[++cnt]=z;
    to[cnt]=y;
    nxt[cnt]=fst[x];
    fst[x]=cnt;
}
struct point{
    ll d;
    int x,y;
    bool operator <(const point t) const{
        return d>t.d;
    }
    point(){}
    point(ll t1,int t2,int t3){
        d=t1,x=t2,y=t3;
    }
};
priority_queue<point> q;
int main(){
    int i,j,n,m,k,x,y,Q;
    ll z,ans;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i)
        for(j=0;j<n;++j)
            dis[i][j]=inf;
    for(i=1;i<=m;++i){
        scanf("%d%d%lld",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    }
    dis[1][0]=0;
    q.push(point(0,1,0));
    while(!q.empty()){
        point p=q.top();
        q.pop();
        if(p.d!=dis[p.x][p.y]||book[p.x][p.y]) continue;
        book[p.x][p.y]=1;
        for(i=fst[p.x];i;i=nxt[i]){
            if(book[to[i]][p.y+1]) continue;
            if(dis[to[i]][p.y+1]>p.d+w[i]){
                dis[to[i]][p.y+1]=p.d+w[i];
                q.push(point(dis[to[i]][p.y+1],to[i],p.y+1));
            }
        }
    }
    scanf("%d",&Q);
    for(i=1;i<=Q;++i){
        scanf("%d",&x);
        for(j=1;j<n;++j){
            scanf("%lld",&b[j]);
            b[j]+=b[j-1];
        }
        ans=inf;
        for(j=0;j<n;++j) ans=min(ans,dis[x][j]+b[j]);
        printf("%lld\n",ans);
    }
    // system("pause");
    return 0;
}

I. 宠物对战

简单题

看到这么多字符串直接想到用trie存,令\(dp[i][0/1]\)表示后缀\(i\)\(A\)组串/\(B\)组串开头的最小拼接代价,然后一边在trie上匹配一边\(DP\)即可。

时间复杂度其实是\(\theta(len^2)\)的(令\(len=strlen(S)\))。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxs 5010
#define inf 0x3f3f3f3f
#define maxn 500010
using namespace std;
char s[maxs];
int trie[2][maxn][26],tot[2];
int flag[2][maxn];
int dp[maxs][2];
void insert(int type,int n,char *p){
    int x=0,i;
    for(i=0;i<n;++i){
        int c=(*p)-'a';
        if(!trie[type][x][c]) trie[type][x][c]=++tot[type];
        x=trie[type][x][c];
        p++;
    }
    flag[type][x]=1;
}
void search(int type,int p,int n){
    int x,i;
    x=trie[type][0][s[p]-'a'];
    for(i=p;i<n;++i){
        if(!x) break;
        int nxt=(i<n-1)?trie[type][x][s[i+1]-'a']:0;
        if(flag[type][x]) dp[p][type]=min(dp[p][type],dp[i+1][type^1]+1);
        x=nxt;
        
    }
    return;
}
int main(){
    int i,j,n,m,L,ans;
    char t[maxn];
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%s",t);
        L=strlen(t);
        insert(0,L,t);
    }
    scanf("%d",&m);
    for(i=1;i<=m;++i){
        scanf("%s",t);
        L=strlen(t);
        insert(1,L,t);
    }
    scanf("%s",s);
    L=strlen(s);
    for(i=0;i<L;++i) dp[i][0]=dp[i][1]=inf;
    for(i=L-1;i>=0;--i){
        search(0,i,L);
        search(1,i,L);
    }
    ans=min(dp[0][0],dp[0][1]);
    if(ans>=inf) printf("-1");
    else printf("%d",ans);
    // system("pause");
    return 0;
}

K. 区间和

有点难,但是典题,做过类似的题就秒了。

注意到权值总和很小,所以容易想到令\(cnt[i]\)表示权值为\(i\)的区间数量,然后做下前缀和找到\(\geq k\)的位置即可。

问题转化成了计算\(cnt[i]\)

\(a\)做前缀和,区间和\([l,r]\)就转化成了\(pre[r]-pre[l-1]\)的两数之差。

选两个数使得差为给定值的方案数,如果把“差”改成“和”,应该很多人会立刻想到生成函数吧。

其实差也是一样的道理,只不过是负指数项。我们统一给它平移一下,相当于乘\(x^k\),最后再给它移回来不就好了?

然后就是上\(FFT\)了。(模数为\(998244353\)的常用NTT会寄,因为\(cnt\)值可能比模数还大qwq)。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define ll long long
#define db double
#define maxn 1000010
using namespace std;
int a[maxn],pos[maxn<<2];
ll cnt[maxn];
const db pi=acos(-1);
struct comp{
    db x,y;
    comp(){}
    comp(db t1,db t2){x=t1,y=t2;}
    comp operator +(comp z){
        return comp(x+z.x,y+z.y);
    }
    comp operator -(comp z){
        return comp(x-z.x,y-z.y);
    }
    comp operator *(comp z){
        return comp(x*z.x-y*z.y,x*z.y+y*z.x);
    }
} w[maxn<<2],A[maxn<<2],B[maxn<<2],C[maxn<<2];
void FFT(comp *L,int n,int lg,int type){
    int i,j,k,t,d;
    for(i=1;i<n;++i) pos[i]=(pos[i>>1]>>1)|((i&1)<<lg-1);
    for(i=0;i<n;++i){
        if(i<pos[i]) swap(L[i],L[pos[i]]);
    }
    db theta=2*pi/n;
    comp z=comp(cos(theta*type),sin(theta*type));
    w[0]=comp(1,0);
    for(i=1;i<n;++i) w[i]=w[i-1]*z;
    for(d=1,t=n>>1;d<n;d<<=1,t>>=1){
        for(j=0;j<n;j+=(d<<1)){
            for(k=0;k<d;++k){
                comp tmp=L[j+k+d]*w[t*k];
                L[j+k+d]=L[j+k]-tmp;
                L[j+k]=L[j+k]+tmp;
            }
        }
    }
}
void multi(comp *L1,comp *L2,comp *res,int n,int lg){
    int i,j;
    FFT(L1,n<<1,lg+1,1);FFT(L2,n<<1,lg+1,1);
    for(i=0;i<(n<<1);++i) res[i]=L1[i]*L2[i];
    FFT(res,n<<1,lg+1,-1);
    for(i=0;i<(n<<1);++i) res[i].x/=(n<<1);
}
int main(){
    int n,i,j,L,m,lg,q,ans;
    ll k;
    db z=0;
    scanf("%d",&n);
    A[0]=comp(1,0);
    for(i=1;i<=n;++i){
        scanf("%d",&a[i]);
        a[i]+=a[i-1];
        A[a[i]]=A[a[i]]+comp(1,0);
    }
    L=a[n];
    for(i=0;i<=L;++i) B[i]=A[L-i];
    for(i=0;i<=L;++i) z+=A[i].x*(A[i].x-1)/2;
    for(m=1,lg=0;m<L+1;m<<=1,++lg);
    multi(A,B,C,m,lg);
    for(i=0;i<=L;++i) cnt[i]=(ll)floor(C[i+L].x+0.5);
    cnt[0]=(ll)floor(z+0.5);
    for(i=1;i<=L;++i) cnt[i]+=cnt[i-1];
    scanf("%d",&q);
    for(i=1;i<=q;++i){
        scanf("%lld",&k);
        ans=lower_bound(cnt,cnt+L+1,k)-cnt;
        printf("%d\n",ans);
    }
    // system("pause");
    return 0;
}

L. 彩色的树

有点难的题,很容易想歪。

发现不管是把树拍成dfs序还是树剖上数据结构都不太能做,于是我们考虑用dsu on tree的技巧。

不会dsu on tree的同学可以看看大佬的博客,讲得很好:dsu on tree入门

考虑一下dsu on tree 题的特征:询问关于子树且没有修改。而本题是满足的。

由于还要考虑深度,我们统计颜色的时候需要额外记录子树内每个深度点的颜色情况,我个人是用multiset存的。

轻儿子的处理就是全部清空,但与传统dsu on tree题的区别在于重儿子的信息不能全部保留,但好消息是,我们发现只需要删除那些卡在距离限制边缘的点(这些点无法贡献到更上层的结点)。

multiset常数很大,但cf的机子非常给力,200多ms就跑完了。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<set>
#define IT multiset<int>::iterator
#define maxn 100010
using namespace std;
int c[maxn],n,m,k;
int fst[maxn],nxt[maxn<<1],to[maxn<<1],cnt=0;
int top[maxn],son[maxn],sz[maxn];
int ans[maxn],deep[maxn],ANS=0,b[maxn];
multiset<int> v[maxn];
void add(int x,int y){
    to[++cnt]=y;
    nxt[cnt]=fst[x];
    fst[x]=cnt;
}
void dfs1(int x,int fa){
    int i;
    deep[x]=deep[fa]+1;
    sz[x]=1;
    for(i=fst[x];i;i=nxt[i]){
        if(to[i]==fa) continue;
        dfs1(to[i],x);
        sz[x]+=sz[to[i]];
        if(sz[to[i]]>sz[son[x]]) son[x]=to[i];
    }
}
void cal(int x,int fa,int flag,int lim,int Son){
    int i;
    if(deep[x]>lim) return;
    if(flag>0){
        if(!b[c[x]]) ANS++;
        v[deep[x]].insert(c[x]);
    }
    else{
        if(b[c[x]]==1) ANS--;
        IT it=v[deep[x]].find(c[x]);
        v[deep[x]].erase(it);
    }
    b[c[x]]+=flag;
    for(i=fst[x];i;i=nxt[i]){
        if(to[i]==fa||flag==1&&to[i]==Son) continue;
        cal(to[i],x,flag,lim,Son);
    }
    return;
}
void del(int x){
    if(x>n) return;
    while(!v[x].empty()){
        IT it=v[x].begin();
        if(b[*it]==1) ANS--;
        b[*it]--;
        v[x].erase(it);
    }
}
void solve(int x,int fa,int type){
    int i;
    for(i=fst[x];i;i=nxt[i]){
        if(to[i]==fa||to[i]==son[x]) continue;
        solve(to[i],x,1);
    }
    if(son[x]) solve(son[x],x,0);
    cal(x,fa,1,deep[x]+k,son[x]);
    ans[x]=ANS;
    if(type) cal(x,fa,-1,deep[x]+k,son[x]);
    else del(deep[x]+k);
    return;
}
int main(){
    int i,j,x,y;
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;++i) scanf("%d",&c[i]);
    for(i=1;i<n;++i){
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    dfs1(1,0);
    solve(1,0,0);
    scanf("%d",&m);
    for(i=1;i<=m;++i){
        scanf("%d",&x);
        printf("%d\n",ans[x]);
    }
    // system("pause");
    return 0;
}
剩下的题以后有空再补吧qwq
posted @ 2022-12-12 16:52  文艺平衡树  阅读(552)  评论(0编辑  收藏  举报