2022“杭电杯”中国大学生算法设计超级联赛(5)题解报告

Problem 1010. Bragging Dice

题意:

两个人玩传统的骰子游戏,现在追加三条规则:

1.如果之前没有人叫过1,则1被视为百搭;

2.如果手上的骰子点数都相同,则认为有额外的一个相同点数的骰子;

3.如果手上的骰子点数互不相同,则认为一个点数也没有。

现在双方知道对方的点数,问先手是否必胜。

题解:

只要先手能叫,必胜。因此,先手叫不了的情况为必败,否则必胜。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;

const int N = 2e5 + 10;
const int mod = 1e9 + 7;

int n, a[N], b[N], c1[8], c2[8];

void solve(){
    cin >> n;
    for(int i = 1; i <= 6;  i++) c1[i] = c2[i] = 0;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) cin >> b[i];
    if(n > 6){
        cout << "Win!\n";
        return;
    }
    for(int i = 1; i <= n; i ++){
        c1[a[i]] ++, c2[b[i]] ++;
        if(c1[a[i]] > 1 || c2[b[i]] > 1){
            cout << "Win!\n";
            return;
        }
    }
    cout << "Just a game of chance.\n";
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1012.Buy Figurines

题意:

若干个人依次去买东西,每个人有一个购买时间。有若干个窗口,每个人到时会选择排队人最少的窗口,否则选择最左边的窗口。问需要多少时间买完。

题解:

模拟题,可以线段树维护。

代码:

#include<iostream>
#include<algorithm>
#include<queue>
#define int long long
#define lson (p << 1)
#define rson (p << 1 | 1)
using namespace std;

const int N = 2e5 + 10;

struct people{
    int s, t;
    bool operator < (const people &x) const{
        return s < x.s;
    }
};

struct shop{
    int t, i;
    bool operator < (const shop &x) const{
        return t > x.t;
    }
};

int n, m;
int time[N], tr[N << 2];
people a[N];

void build(int l = 1, int r = m, int p = 1){
    tr[p] = 0;
    if(l == r) return;
    int mid = (l + r) >> 1;
    build(l, mid, lson);
    build(mid + 1, r, rson);
}

void update(int x, int y, int l = 1, int r = m, int p = 1){
    if(l == r){
        tr[p] += y;
        return;
    }
    int mid = (l + r) >> 1;
    if(x <= mid) update(x, y, l, mid, lson);
    else update(x, y, mid + 1, r, rson);
    tr[p] = min(tr[lson], tr[rson]);
}

int query(int l = 1, int r = m, int p = 1){
    if(l == r) return l;
    int mid = (l + r) >> 1;
    if(tr[lson] <= tr[rson]) return query(l, mid, lson);
    else return query(mid + 1, r , rson);
}

void solve(){
    cin >> n >> m;
    for(int i = 1; i <= m; i++) time[i] = 0;
    build();
    for(int i = 1; i <= n; i++){
        cin >> a[i].s >> a[i].t;
    }
    sort(a + 1, a + 1 + n);
    priority_queue<shop> q;
    for(int i = 1; i <= n; i++){
        while(!q.empty() && q.top().t <= a[i].s){
            update(q.top().i, -1);
            q.pop();
        }
        int tmp = query();
        update(tmp, 1);
        q.push({max(time[tmp] + a[i].t, a[i].s + a[i].t), tmp});
        time[tmp] = max(time[tmp] + a[i].t, a[i].s + a[i].t);
    }
    int ans = 0;
    for(int i = 1; i <= m; i++){
        ans = max(ans, time[i]);
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1003.Slipper

题意:

求s到t的最短路,其中任意两个点如果深度差等于k,则可以花费p来进行传送。

题解:

在满足深度差的层之间增加新的边,用dijstra跑最短路即可。

代码:

#include<iostream>
#include<set>
#include<cstring>
#include<queue>
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;

const int N = 1e6 + 10;

int n, top, s, t, k, p;
int head[N], dep[N], dis[N], vis[N], jump[N];

set<int> now[N];

struct edge{
    int to, cost, next;
}e[N];

struct node{
    int num, dis;
    bool operator < (const node &x) const{
        return dis > x.dis;
    }
};

void init(){
    top = 0;
    memset(head, -1, sizeof(head));
    memset(dis, INF, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    memset(jump, 0, sizeof(jump));
    for(int i = 1; i <= n; i++) now[i].clear();
}

void add(int u, int v, int w){
    e[top].to = v;
    e[top].cost = w;
    e[top].next = head[u];
    head[u] = top ++;
}

void dfs(int u, int fa){
    now[dep[u]].insert(u);
    for(int i = head[u]; ~i; i = e[i].next){
        if(e[i].to == fa) continue;
        dep[e[i].to] = dep[u] + 1;
        dfs(e[i].to, u);
    }
}

int dijkstra(){
    priority_queue<node> q;
    q.push({s, 0});
    dis[s] = 0;
    while(!q.empty()){
        int u = q.top().num; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        if(u == t) return dis[t];
        for(int i = head[u]; ~i; i = e[i].next){
            int v = e[i].to, w = e[i].cost;
            if(dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                q.push({v, dis[v]});
            }
        }
        if(jump[dep[u]]) continue;
        jump[dep[u]] = 1;
        for(auto v : now[dep[u] + k]){
            if(dis[v] > dis[u] + p){
                dis[v] =dis[u] + p;
                q.push({v, dis[v]});
            }
        }
        if(dep[u] - k <= 0) continue;
        for(auto v : now[dep[u] - k]){
            if(dis[v] > dis[u] + p){
                dis[v] =dis[u] + p;
                q.push({v, dis[v]});
            }
        }
    }
    return 0;
}

void solve(){
    cin >> n;
    init();
    int u, v, w;
    for(int i = 1; i < n; i++){
        cin >> u >> v >> w;
        add(u, v, w);
        add(v, u, w);
    }
    cin >> k >> p >> s >> t;
    dep[1] = 1; 
    dfs(1, 0);
    cout << dijkstra() << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1007.Count Set

题意:

给定一个排列,要求计算有多少种取法,取长度为k的子排列,使得以子排列为下标的原排列与该子排列的交集为空。

题解:

假设有一个n个点的图,排列p中的某个项pi表示从点i到点p有一条边。则可以发现这是一个由若干不交有向环组成的图。
则题目中的条件可以等价于,从每个环中挑选若干个点,使得每个环中被选出的点不相邻,从一个大小为m的环中选出k个点的方案数是C_{m - k}^{k} +C_{m - k - 1}^{k - 1}(我当时赛场上推的是2C_{m}^{k} - C_{m - 1}^{k}),因为可以把被选出的点当作大小为2的段,然后不选的点当作大小为1的段,用大小为2保证被选的点不相邻,问题就转化成总共有m - k个块,挑出k个块的方案数,如果在序列上,这就是一个组合数,在环上也只需要挑一个位置拆开,变成序列问题,然后分有没有大小为2的块恰好在断开处讨论一下,发现就是上面的式子。
推出这个式子之后只需要列出每个环的生成函数然后 NTT 合并就好了 。

代码:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<map>
#include<set>
#include<queue>
using namespace std;
#define debug(x) cout<<#x<<": "<<(x)<<endl
#define debug2(x,y) cout<<#x<<":"<<endl;for(int i=1;i<=y;++i)cout<<x[i]<<" ";cout<<endl
#define mem(x,y) memset(x,y,sizeof(x));
#define int long long
#define double long double
#define lson (p<<1)
#define rson (p<<1|1)
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
const double PI=acos(-1.0);
const int maxn=2e6+10;
const int mod=998244353;//1e9+7
const int G=3,GI=332748118;//5
int prt[maxn][2];
int invlen[maxn];

int n,k;
int a[maxn];
bool vis[maxn];
int tot=0,ff[maxn],ll[maxn<<2],rr[maxn<<2],sum[maxn];
int fac[maxn],inv[maxn];
int A[maxn],B[maxn],res[maxn];
int rev[maxn];

int bitpow(int x,int y)
{
    int res=1;
    while(y)
    {
        if(y&1)res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}

void init(int len)
{
    for(int i=1;i<len;++i)
    {
        rev[i]=rev[i>>1]>>1;
        if(i&1)rev[i]|=len>>1;
    }
}

void NTT(int *f,int len,int flg)
{
    for(int i=0;i<len;++i)
    {
        if(i<rev[i])
        swap(f[i],f[rev[i]]);
    }
    for(int l=2;l<=len;l<<=1)
    {
        int gn=prt[l][(int)(flg==1)];
        for(int j=0;j<len;j+=l)
        {
            int g=1;
            for(int k=j;k<j+l/2;++k)
            {
                int x=f[k];
                int y=g*f[k+l/2]%mod;
                f[k]=(x+y)%mod;
                f[k+l/2]=(x-y+mod)%mod;
                g=g*gn%mod;
            }
        }
    }
    if(flg==-1)
    {
        int inv=invlen[len];
        for(int i=0;i<len;++i)
        f[i]=f[i]*inv%mod;
    }
}

void initinit()
{
    fac[0]=1;
    for(int i=1;i<maxn;++i)
    fac[i]=fac[i-1]*i%mod;
    inv[maxn-1]=bitpow(fac[maxn-1],mod-2);
    for(int i=maxn-2;i>=1;--i)
    inv[i]=inv[i+1]*(i+1)%mod;
    inv[0]=1;
    for(int i=2;i<maxn;i<<=1)
    {
        prt[i][1]=bitpow(G,(mod-1)/i);
        prt[i][0]=bitpow(GI,(mod-1)/i);
        invlen[i]=bitpow(i,mod-2);
    }
}

int C(int x,int y)
{
    if(x<y)return 0;
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}

void get(int ii,int siz)
{
    for(int i=0;i<=siz/2;++i)
    {
        ff[tot++]=(2*C(siz-i,i)%mod-C(siz-i-1,i)+mod)%mod;
    }
    sum[ii]=tot-1;
}

void solve(int l,int r,int p)
{
    if(l==r)
    {
        ll[p]=sum[l-1]+1;
        rr[p]=sum[l];
        return;
    }
    int mid=(l+r)>>1;
    solve(l,mid,lson);
    solve(mid+1,r,rson);
    ll[p]=ll[lson];rr[p]=rr[rson];
    int len=1;
    while(len<rr[p]-ll[p]+1)len<<=1;
    init(len);
    for(int i=0;i<len;++i)
    A[i]=B[i]=res[i]=0;
    for(int i=ll[lson];i<=rr[lson];++i)
    A[i-ll[lson]]=ff[i];
    for(int i=ll[rson];i<=rr[rson];++i)
    B[i-ll[rson]]=ff[i];
    NTT(A,len,1);NTT(B,len,1);
    for(int i=0;i<len;++i)
    res[i]=(A[i]*B[i]%mod+mod)%mod;
    NTT(res,len,-1);
    for(int i=ll[p];i<=rr[p];++i)
    ff[i]=res[i-ll[p]];
    // for(int i=ll[p];i<=rr[p];++i)
    // cout<<f[i]<<" ";
    // cout<<'\n';
}

void work()
{
    tot=0;
    cin>>n>>k;
    for(int i=1;i<=n;++i)
    vis[i]=0;
    for(int i=1;i<=n;++i)
    cin>>a[i];
    int cntcir=0;
    sum[0]=-1;
    for(int i=1;i<=n;++i)
    {
        if(vis[i])continue;
        vis[i]=1;
        int now=i,cnt=1;
        while(vis[a[now]]==0)
        {
            now=a[now];
            ++cnt;
            vis[now]=1;
        }
        if(cnt==1)continue;
        get(++cntcir,cnt);
    }
    if(cntcir==0)
    {
        if(k)cout<<0<<'\n';
        else cout<<1<<'\n';
        return;
    }
    solve(1,cntcir,1);
    cout<<ff[k]<<'\n';
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // cout<<fixed<<setprecision(6);
    initinit();
    int _=1;
    cin>>_;
    while(_--)
    work();
    return 0;
}

Problem 1006. BBQ

题意:

给定一个字符串,问最少的操作次数,使得操作后的字符串每四个一队都是ABBA的形式。

代码:

#include<stdio.h>
#include<string.h>
#include<random>

#define U unsigned
#define LL long long
#define UL U LL


int t[10];
int g[10][5];
char w[3000000];

void dfs(int n,int c,int idx)
{
    int m=9999999;
    if(n)
    for(int a=1;a<=7;a++)
    for(int b=1;b<=7;b++)
    {
        int p[5]={0,a,b,b,a};
        memset(g,1,sizeof g);
        for(int i=0;i<=4;i++)
            g[0][i]=i;
        for(int i=0;i<=7;i++)
            g[i][0]=i;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=4;j++)
                g[i][j]=std::min(std::min(g[i-1][j]+1,g[i-1][j-1]+(t[i]!=p[j])),g[i][j-1]+1);
        if(g[n][4]<m)m=g[n][4];
    }
    if(n)w[idx]=m;
    if(n==7)return;
    n++;
    for(int i=1;i<=c;i++)
    {
        t[n]=i;
        dfs(n,c,idx*8+i);
    }
    t[n]=c+1;
    dfs(n,c+1,idx*8+c+1);
}

char s[1000001];
int dp[1000001];
int last[26];
int pre[1000001];
void sol()
{
    scanf("%s",s+1);
    int n=strlen(s+1);
    memset(dp+1,10,n*4);
    memset(last,-1,sizeof last);

    for(int i=1;i<=n;i++)
    {
        pre[i]=last[s[i]-'a'];
        last[s[i]-'a']=i;
    }

    for(int i=0;i<n;i++)
    {
        dp[i+1]=std::min(dp[i+1],dp[i]+1);
        int cnt=0;
        int idx=0;
        int tmp[8];

        for(int j=1;j<=7&&i+j<=n;j++)
        {
            //int c=s[i+j]-'a';
            if(pre[i+j]<=i)
                idx=idx*8+ (tmp[j]=++cnt);
            else
                idx=idx*8+ (tmp[j]=tmp[pre[i+j]-i]);
            dp[i+j]=std::min(dp[i+j],dp[i]+w[idx]);
        }
    }
    printf("%d\n",dp[n]);
}

int main()
{
    dfs(0,0,0);
    int t;
    scanf("%d",&t);
    while(t--)sol();
}

Problem 1002. Jo loves counting

题意:

对于一个数,d为它的一个因数且这个数所有的质因数也是d的因数,随机选择一个满足的d,如果d等于这个数本身,则获得d分,否则0分。现在给定一个M,问在1 ~ M中选一个数,获得的分数的期望。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define de(x) cout << #x <<" = "<<x<<endl
#define dd(x) cout << #x <<" = "<<x<<" "
const ll P=29ll<<57|1;
const int Lim=1e6, MAXN=Lim+10; 
int fc[MAXN], prime[MAXN/10], cntprime;
ll inv[64];
inline ll mul(ll a,ll b,ll p=P) { 
    return (a*b-(ull)((long double)a/p*b)*p+p)%p; 
}
inline ll kpow(ll a, ll x) { 
    ll ans=1; 
    for(;x;x>>=1, a=mul(a, a)) 
        if(x&1) ans=mul(ans, a); 
    return ans; 
}
inline ll h(ll p, int k, ll pk) {
    return P-mul(pk, mul(inv[k], inv[k-1]));
}
inline ll PN_sieve(const ll n, const int flr, const ll hd) {
    ll res=mul(mul(n, mul(n+1, inv[2])), hd);
    for(int i=flr+1; i<=cntprime; ++i) {
        int p=prime[i], k=1;
        ll val=n/p, pk=p;
        if(val<p)
            break;
        while(val>=p) {
            val/=p;
            pk*=p;
            ++k;
            
            res+=PN_sieve(val, i, mul(hd, h(p, k, pk)));
            if(res>=P)
                res-=P;
        }
    }
    return res;
}
inline void sieve() {
    for(int i=2; i<=Lim; ++i) {
        if(!fc[i]) fc[i]=prime[++cntprime]=i;
        for(int j=1; j<=cntprime; ++j)
            if(prime[j]>fc[i]||prime[j]*i>Lim)
                break;
            else
                fc[prime[j]*i]=prime[j];
    }
}
inline void init() {
    sieve();
    for(int i=1; i<64; ++i)
        inv[i]=kpow(i, P-2);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    init();
    int T; cin>>T;
    ll M;
    while(T--) {
        cin>>M;
        cout<<mul(PN_sieve(M, 0, 1), kpow(M, P-2))<<"\n";
    }
    cout.flush();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值