2021ccpc网络赛重赛

05 Monopoly

题意:有n个点,每个点有一个分数。兔子从0开始一个点一个点的往前跳,跳到这个点就会拿到这个点的分数,跳到n点下一步会跳回1然后继续跳。给定目标分数,问兔子最少要跳几次才能达到这个目标分数。

先求前缀和,原序列n个数的和为sumall。先讨论sumall>0的情况。目标分数x=sum[i]+k*sumall,所以就有x%sumall=sum[i]%sumall,所以要找到符合条件的sum[i]我们就要在和x对于sumall同余的数sum[i]中找,又因为一开始的x=sum[i]+k*sumall,要求最小步数所以要使k最小那么就要找到最大的sum[i],当然sum[i]要小于x。这一步可以用二分,然后再带入式子看找到的这个数是否符合,不符合则无解。对于sumall<0的情况就把原式和x取反就行。对于0的情况就是如果一圈找不到那就是找不到。
我是用map套vector写的,对于取模的地方可能会遇见x取模为正,sum[i]取模为负的情况,所以取模都取正数,因为在二分查找的时候我们还是直接查找小于x的第一个数,所以不影响。

#include<iostream>
#include<cstring>
#include<stdio.h>              
#include<queue>
#include<math.h>
#include<algorithm>
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sfl(x) scanf("%lld",&x)
#define pf(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
#define debug(x) cout << #x << " = " << x << "\n"
#define lson l,mid,k<<1
#define rson mid+1,r,k<<1|1
const int Ni=200;
const int N=1e5+10;
const int mod=998244353;
using namespace std;
int n;
ll sumall;
struct point{
    ll num;
    int idx;
}node,sum[N];
map<int, vector<point> >mp;
bool cmp(const point &a, const point &b){
    if(a.num == b.num) return a.idx > b.idx;
    return a.num < b.num;
}
void init(){
    for(int i=1;i<=n;i++){
      ll x=1ll*sum[i].num%sumall;
      if(x<0) x=(x+sumall)%sumall;
      mp[x].clear();
    }
    for(int i=1;i<=n;i++){
        ll x=1ll*sum[i].num%sumall;
        if(x<0) x=(x+sumall)%sumall;
      //  printf("x=%d y=%d\n",x,sum[i].num);
        node.idx=sum[i].idx,node.num=sum[i].num;
        mp[x].push_back(node);
    }
    for(auto &v:mp) {
        sort(v.second.begin(), v.second.end(), cmp);
    }
}
ll ef(ll x,ll xx,ll yy){
  //  printf("xx=%d\n",xx);
    if(!mp[xx].size()){
        return -1*1ll;
    }
    int l=0,r=mp[xx].size()-1;
  //  printf("l=%d r=%d\n",l,r);
    while (l<r){
        int mid=(l+r+1)>>1;
        if(mp[xx][mid].num<=yy) l=mid;
        else r=mid-1;
     //   printf("l=%d r=%d\n",l,r);
    }
    if(mp[xx][l].num>yy){
        return -1*1ll;
    }
    ll k=(x-mp[xx][l].num)/sumall;
    ll y=k*sumall+mp[xx][l].num;
  //  printf("y=%d k=%d sum[%d].num=%d idx=%d\n",y,k,mp[xx][l].idx,sum[mp[xx][l].idx].num,sum[mp[xx][l].idx].idx);
    ll ans=0;
    if((x-mp[xx][l].num)%sumall==0&&y==x&&k>=0){
      //  printf("__ y=%d\n",y);
        return ans=1ll*k*n+sum[mp[xx][l].idx].idx;
    }
    else return -1*1ll;
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int m;
        scanf("%d%d",&n,&m);
        sumall=0;
        for(int i=1;i<=n;i++){
            ll a;
            scanf("%lld",&a);
            sumall=sumall+a;
         //   printf("sumall=%lld\n",sumall);
            sum[i].num=sumall;
            sum[i].idx=i;
        }
        int flag=0;
        if(sumall<0){
            for(int i=1;i<=n;i++) sum[i].num=sum[i].num*(-1);
            sumall=sumall*(-1);
            flag=1;
        }
        if(sumall==0){
            sort(sum+1,sum+n+1,cmp);
            for(int i=0;i<m;i++){
                ll x;
                sfl(x);
                if(flag) x=x*-1;
                if(x==0){
                    printf("0\n");
                    continue;
                }
                int l=1,r=n;
                while(l<r){
                    int mid=(l+r+1)>>1;
                    if(sum[mid].num<=x) l=mid;
                    else r=mid-1;
                  //  printf("l=%d r=%d\n",l,r);
                }
                if(sum[l].num==x) printf("%lld\n",sum[l].idx);
                else printf("-1\n");
            }
            continue;
        }
        init();
        for(int i=0;i<m;i++){
            ll x;
            scanf("%lld",&x);
            if(flag) x=x*(-1);
         //   debug(x);
            ll flag1=0;
            if(x==0){
                printf("0\n");
                continue;
            }
            ll xx=(ll)(x%sumall);
            ll yy=x;
            if(xx<0) xx=(xx+sumall)%sumall;
          //  printf("xx=%d yy=%d\n",xx,yy);
            flag1=ef(x,xx,yy);
            printf("%lld\n",flag1);
        }
    }
    return 0;
}
/*
被wa在了多组样例上。
对负数取模不好处理,如果sumall为负数就全都取反。求xx的时候要取模,如果取模是负数可以化成正数来解决
因为二分查找的时候还是查找这个数,取模为正数就不用多找一次了。
*/

06 Nun Heh Heh Aaaaaaaaaaa

题意:满足前缀为“nunhehheh”,后缀全为a的串就是目标串,给一个主串问有多少个子串是目标串(子串定义为从主串中删去任意元素剩下的元素就是子串)

dp计算每个i对应的方案数,zl[i][j]记录的是主串匹配到i,子串以j为结尾时的目标串数量。zl[i][j]=zl[i-1][j]+zl[i-1][j-1],就是它先继承它的上一位匹配到j时的数量,然后因为第i位也匹配,所以它还要加上多了一个匹配字母时的数量,这个数量就是sum[i-1][j-1],就是它之前匹配到i-1时的数量。
然后dp[i][j]就是记录主串有多少个以i结尾的匹配到j的目标串,然后乘的时候乘a就行。a就是有n个a,每个a可以选或者不选,再减去都不选的1。

#include<iostream>
#include<cstring>
#include<stdio.h>              
#include<queue>
#include<math.h>
#include<algorithm>
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sfl(x) scanf("%lld",&x)
#define pf(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
#define debug(x) cout << #x << " = " << x << "\n"
#define lson l,mid,k<<1
#define rson mid+1,r,k<<1|1
const int N=1e5+10;
const int mod=998244353;
using namespace std;
char str[N],s[10]="nunhehheh";
ll dp[N][10],zl[N][10];
ll suma[N];
int main(){
    int t;
    sf(t);
    while(t--){
        scanf("%s",str);
        int len=strlen(str);
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=len;i++){
            for(int j=1;j<=9;j++){
                if(str[i-1]==s[j-1]){
                    zl[i][j]=(zl[i-1][j-1]+zl[i-1][j])%mod;
                    if(j==1) zl[i][j]++;
                    dp[i][j]=zl[i-1][j-1];
                  //  printf("zl[%d][%d]=%d zl[%d][%d]=%d\n",i-1,j,zl[i-1][j],i,j,zl[i][i]);
                }
                else zl[i][j]=zl[i-1][j];
             //   printf("zl[%d][%d]=%d\n",i,j,zl[i][j]);
            }
        }
        ll res=1;
        for(int i=len;i>0;i--){
            if(str[i-1]=='a'){
                res=(res*2)%mod;
            }
            suma[i]=(res-1+mod)%mod;
        }
        ll ans=0;
        for(int i=1;i<=len;i++){
            if(dp[i][9]) ans=(ans+(dp[i][9]*suma[i])%mod)%mod;
        }
        pfl(ans);
    }
    return 0;
}

/*
5
nunhehhehiha
nunhehhehahaahahahahahahaahaahahahahha
nunhehhehhehhahaahahahaahaahahaaaahaa
nunhehhehhnunhehhehha
wa在了取模,998244353看成了1e9+7。
*/

08 Subpermutation

题意:给一个数字n,n的全排列按字典序生成一个串。然后给一个数字m,子串t的所有情况就是m的所有排列方式。要求在n组成的主串中有多少子串t

答案分成两种,一种是直接在串内的,就是串内m个数全排列,剩下(n-m)个数也全排列然后把子串插入剩下串中,(n-m)个元素一共有(n-m+1)个空,所以是m!(n-m)!(n-m+1)。
然后就是在两个串中间的答案。
我们可以知道字典序最小的串的性质就是当前串的最长递减后缀(注意是后缀,比如12435的最长递减后缀就是5)的前一位数字和这个后缀中第一个大于这个数字的数交换就是下一个串,比如12435下一个就是12453.
我们把串分为三个部分,首部、中部、尾部。
我们可以知道如果一个串的首部和尾部都是<=m的元素,中部>m,并且它的下一个串的首部元素不变,那么这两个串就可以构成一个子串t。这个地方要怎么求呢?我们先求出满足首部尾部<=m,中部>m的串,就是m个数字全排列m!和(n-m)个数字全排列(n-m)!,然后这(n-m)个数插入到m个数中,就是m!(n-m)!(m-1),再减去下一个串首部会变的元素。要首部改变,那么就是首部的元素在最长递减后缀的前一位,就说明中部和尾部一定要为递减的,所以就是枚举尾部元素的个数从1到m-1,C(m,i)因为是递减所以只有一种排列方式,然后中部元素是固定的也只有一种排列方式,首部元素全排列(m-i)!所以就是:m!(n-m)!(m-1)-[i:1-m-1]C(m,i)*(m-i);那么不满足首部和尾部都是<=m的元素,中部>m,并且它的下一个串的首部元素不变的条件有没有可能构成答案呢?如果满足的话那么下一个串的首部元素就需要改变。当前串要符合的话尾部元素必须<=m,所以我们假设中部元素<=m,那么就不可能构成中部和尾部合起来的串是最长递减后缀并且此后缀的前一个元素还小于后缀中<=m的元素。所以这种情况就不可能。

#include<iostream>
#include<cstring>
#include<stdio.h>              
#include<queue>
#include<math.h>
#include<algorithm>
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sfl(x) scanf("%lld",&x)
#define pf(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
#define debug(x) cout << #x << " = " << x << "\n"
#define lson l,mid,k<<1
#define rson mid+1,r,k<<1|1
const int N=1e6+10;
const int mod=1e9+7;
using namespace std;
ll fact[N],infact[N];
ll sum[N];
ll qmi(ll a,ll b){
    ll res=1;
    while (b){
        if(b&1) res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return res;
}
void jie(){
    fact[0]=infact[0]=1;
    ll sumi=0;
    for(int i=1;i<N;i++){
        fact[i]=(ll)fact[i-1]*i%mod;
        infact[i]=(ll)infact[i-1]*qmi(i,mod-2)%mod;
        sumi=(sumi+infact[i])%mod;
        sum[i]=sumi;
    }
}
int main(){
    int t;
    scanf("%d",&t);
    jie();
    while (t--){
        int n,m;
        scanf("%d%d",&n,&m);
        ll ans1=(((fact[m]*fact[n-m])%mod)*(n-m+1))%mod;
        ll ans2=((((fact[m]*fact[n-m])%mod)*(m-1))%mod-(sum[m-1]*fact[m])%mod+mod)%mod;
        ll ans=(ans1+ans2)%mod;
      //  debug(fact[1000000]);
        printf("ans1=%d ans2=%d\n",ans1,ans2);
        pfl(ans);
    }
    return 0;
}

/*
12345 12354 12435 12453 12534 12543 13245 13254 13425 13452 13524 13542 14235 14253 14325 14352 14523 14532 15234 15243 15324 15342 15423 15432 
21345 21354 21435 21453 21534 21543 23145 23154 23415 23451 23514 23541 24135 24153 24315 24351 24513 24531 25134 25143 25314 25341 25413 25431
*/

10 Bigraph Extension

题意:有2n个点,n是偶数。n个点属于A集合,n个点属于B集合。初始的时候会连m条边,初始连边的时候保证每个点最多只会连一条边。要求全图任意两个点之间都能有一条简单路径的路径长度为大于n,简单路径就是路径中每个点只经过一次。求最少还需要连几条边。输出边,边构成的集合的字典序最小(边权全为1)

要所有点都能有两条路那么就是要全连通,每个点只用连两条边就能全连通。但是要求字典序最小所以要判连通块,如果当前点已经在集合中就不能再加入一次,否则会提前成环。画一画图就懂了。

#include<iostream>
#include<cstring>
#include<stdio.h>              
#include<queue>
#include<math.h>
#include<algorithm>
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sfl(x) scanf("%lld",&x)
#define pf(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
#define debug(x) cout << #x << " = " << x << "\n"
#define lson l,mid,k<<1
#define rson mid+1,r,k<<1|1
const int N=1e3+10;
const int mod=998244353;
using namespace std;
int fa[N*2],edge[N*2];
int findi(int a){
    if(fa[a]==a) return a;
    return fa[a]=findi(fa[a]);
}
int main(){
    int t;
    sf(t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=2*n;i++){
            fa[i]=i;
            edge[i]=0;
        }
        for(int i=0;i<m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            fa[v+n]=u;
            edge[u]++;
            edge[v+n]++;
        }
        printf("%d\n",n*2-m);
        int st=n+1,ed=n;
        for(int i=1;i<=n;i++){
            if(edge[i]>=2) continue;
            while(edge[st]>=2) st++;
            for(int j=st;j<=2*n;j++){
                int fx=findi(i);
                int fy=findi(j);
                if(fx!=fy&&edge[j]!=2){
                    edge[i]++;
                    edge[j]++;
                    if(fx>fy) swap(fy,fx);
                    fa[fy]=fx;
                    printf("%d %d\n",i,j-n);
                }
                if(edge[i]>=2) break;
            }
            if(edge[i]!=2) ed=i;
        }
        while(edge[st]>=2) st++;
        printf("%d %d\n",n,st-n);
    }
    return 0;
}
/*
5
5 2
2 4
5 5
5 1
2 4
*/

11 Jumping Monkey

题目:有一颗有n个点n-1条边的树,每个点都有一个点权。猴子可以在树上从一个点跳到另一个点。他只有在v点是u到v的路径中点权最大的点的时候才能从u点跳到v点。求猴子从每个点开始能向外跳的最多的点的个数。

计算贡献。反过来思考,如果从最大的点开始就没有任何一个点可以跳,从第二大的点开始除了最大的点也没有任何点可以跳。但是这样不好写,所以不妨正过来写,先排序然后从最小的点开始一个个把小于当前点的最大点找出来放入vector中,因为是排过序的所以可以保证后面的点一定大于前面的点。记录小于这个点的最大点的方法是用并查集不断更新父节点,然后重新dfs建一颗树,每个点所在的层数就是答案。

#include<iostream>
#include<cstring>
#include<stdio.h>              
#include<queue>
#include<math.h>
#include<algorithm>
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sfl(x) scanf("%lld",&x)
#define pf(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
#define debug(x) cout << #x << " = " << x << "\n"
#define lson l,mid,k<<1
#define rson mid+1,r,k<<1|1
const int Ni=200;
const int N=1e5+10;
const int mod=998244353;
using namespace std;
struct point{
    int idx,val;
}node[N];
vector<int> edges[N],g[N];
int fa[N],a[N],ans[N];
int cmp(point a,point b){
    return a.val<b.val;
}
int findi(int a){
    if(fa[a]==a) return a;
    return fa[a]=findi(fa[a]);
}
void dfs(int u,int f){
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==f) continue;
        ans[v]=ans[u]+1;
        dfs(v,u);
    }
}
int main(){
    int t;
    sf(t);
    while(t--){
        int n;
        sf(n);
        for(int i=1;i<=n;i++){
            fa[i]=i;
            ans[i]=1;
            edges[i].clear();
            g[i].clear();
        }
        for(int i=1;i<=n-1;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            edges[u].push_back(v);
            edges[v].push_back(u);
        }
        for(int i=1;i<=n;i++){
            sf(node[i].val);
            node[i].idx=i;
            a[i]=node[i].val;
        }
        sort(node+1,node+1+n,cmp);
        for(int i=1;i<=n;i++){
            int u=node[i].idx;
           // printf("edge[%d].size=%d\n",u,edges[u].size());
            for(int j=0;j<edges[u].size();j++){
                int v=edges[u][j];
              //  printf("u=%d v=%d\n",u,v);
                if(a[u]<a[v]) continue;
                int fv=findi(v);
                g[u].push_back(fv);
                fa[fv]=u;
            }
        }
        dfs(node[n].idx,-1);
        for(int i=1;i<=n;i++){
            printf("%d\n",ans[i]);
        }
    }
    return 0;
}
/*
2
3
1 2
2 3
1 2 3
5
1 2
1 3
2 4
2 5
1 4 2 5 3
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值