CCPC2024jinan

前言

又是差罚时能偷银,唉,还是实力不够,这场感觉可写的不少,和之前 vp 南京那场差不多

A. The Fool

刚开始一眼以为找 QWQ 差点交了,还好考虑了一下,实际是一堆相同的串,里面会有一个串与其他串不同,输出那个串的位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bits/stdc++.h>
using namespace std;
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    string s[n+1];
    map<string,vector<pair<int,int>>>mp;
    for(int i=1;i<=n;i++)cin>>s[i];
    for(int i=1;i<=n;i++){
        int cnt=1;
        for(int j=0;j<(int)s[i].size();j+=k){
            string tmp=s[i].substr(j,k);
            mp[tmp].push_back({i,cnt});
            cnt++;
        }
    }
    for(int i=1;i<=n;i++){
        int cnt=1;
        for(int j=0;j<(int)s[i].size();j+=k){
            string tmp=s[i].substr(j,k);
            if(mp[tmp].size()==1){
                cout<<i<<" "<<cnt<<endl;
                return 0;
            }
            cnt++;
        }
    }
}

J. Temperance

被题意哈住了,实际上先删值小的并不会影响到后面的,因为如果他和后面值更大的在同一行或列,他也是那个大值,而不是这个小的值

所以只需要统计小于 $i$ 的值有多少个即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7;

int a[N],b[N],c[N];
int n;

void solve(){
    map<int,int>x,y,z;
    vector<int>s;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i]>>b[i]>>c[i];
        x[a[i]]++;
        y[b[i]]++;
        z[c[i]]++;
    }
    for(int i=1;i<=n;i++){
        int tmp=-1;
        tmp=max({x[a[i]]-1,y[b[i]]-1,z[c[i]]-1});
        s.push_back(tmp);
    }
    sort(s.begin(),s.end());
    s.push_back(N+50);
    int cnt=0;
    for(int i=0;i<n;i++){
        while(s[cnt]<i&&cnt<(int)s.size())cnt++;
        cout<<cnt<<" ";
    }
    cout<<"\n";
}

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

B. The Magician

一共 $52$ 张牌,每种牌最多 $13$ 张,最多剩下 $3$ 张,即一共最多剩下 $12$ 张,要求的其实就是用这 $12$ 张能再拼出几个同花顺

队长大模拟两发过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int cnt[5],id[1000],flag[5],num=0;
bool vis[5];
bool sol()
{
    int sum=0,now[5],tmp=num,ff[5];
    for(int i=1;i<=4;++i) {
        if(!vis[i])  sum+=cnt[i];
        now[i]=cnt[i];
        ff[i]=flag[i];
    }
    for(int i=1;i<=4;++i) {
        if(!vis[i])  continue;
        if(ff[i]){
            int x=min(3,5-now[i]);
            now[i]+=x;
            sum-=x;
            ff[i]=0;
        }
        if(now[i]==5)  continue;
        int x=min(tmp,5-now[i]);
        now[i]+=x;
        tmp-=x;sum-=x;
        if(now[i]==5)  continue;
        else  return 0;
    }
    return 1;
}
bool dfs(int k)
{
    if(k==0) {
        return  sol();
    }
    for(int i=1;i<=4;++i) {
        if(vis[i])  continue;
        vis[i]=1;
        bool x=dfs(k-1);
        if(x)  return 1;
        vis[i]=0;
    }
    return 0;
}
void solve()
{
    num=0;
    for(int i=1;i<=4;++i)  cnt[i]=flag[i]=0,vis[i]=0;
    int n,ans=0;
    cin>>n;
    for(int i=1;i<=n;++i) {
        string s;
        cin>>s;
        ++cnt[id[s[1]]];
    }
    for(int i=1;i<=4;++i) {
        ans+=cnt[i]/5;
        cnt[i]%=5;
    }
    for(int i=1;i<=4;++i)  cin>>flag[i];
    for(int i=1;i<=2;++i) {
        int x;
        cin>>x;
        num+=x;
    }
    int tot=0;
    for(int i=1;i<=4;++i) {
        tot+=cnt[i];
    }
    for(int i=tot/5;i>=1;--i) {
        bool x=dfs(i);
        if(x) {
            ans+=i;
            break;
        }
    }
    printf("%d\n",ans);
    
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    id['D']=1;id['C']=2;id['H']=3;id['S']=4;
    int T=1;
    cin>>T;
    
    while(T--) {
        solve();
    }
    return 0;
}

F. The Hermit

加的思路

开始我想的是统计每种数可能被删的次数,最后在所有的答案中减去这部分

但是想错了一点就是只考虑了删前几位,没有考虑倍增时,前面的几个都能删

队长想的是按长度分开,前面的部分是倍增关系需要删的,后面部分是留下的,加到答案上的

前面的片段的长度只可能为 $O(\log n)$,同时确定前面片段的最大值之后,后面的片段只需要按组合数计算得出数量即可

每次加前面片段的种类数量乘以后面片段的种类数量即可

口胡得到这个应该是 $O(n \log n \log n)$ 的来着,实际交上去只跑了 $200$ 多 $ms$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+7,mod=998244353;
ll jc[N],inv[N],f[N][20],n,m,g[N][20],id[N];
ll qpow(ll x,ll k)
{
    ll ans=1,tmp=x;
    while(k) {
        if(k&1)  ans=ans*tmp%mod;
        tmp=tmp*tmp%mod;
        k>>=1;
    }
    return ans;
}
ll C(int x,int y)
{
    if(x<y)  return 0;
    return jc[x]*inv[y]%mod*inv[x-y]%mod;
}
ll calc(int x,int y)
{
    if(x<y)  return 0;
    if(y==0)  return 0;
    if(g[id[x]][n-y]!=-1)  return g[id[x]][n-y];
    ll sum=0;
    for(int i=1;i<=x;++i) {
        sum=(sum+C(x/i-1,y-1))%mod;
    }
    sum=(C(x,y)-sum+mod)%mod;
    g[id[x]][n-y]=sum;
    return sum;
}
void solve()
{
    memset(g,-1,sizeof(g));
    int tot=0;
    cin>>m>>n;
    inv[0]=jc[0]=1;
    for(int i=1;i<=m;++i) {
        jc[i]=jc[i-1]*i%mod;
        inv[i]=inv[i-1]*qpow(i,mod-2)%mod;
        if(!id[m/i])  id[m/i]=++tot;
    }
    ll ans=calc(m,n)*n%mod;
    for(int i=1;i<=m;++i) {
        f[i][1]=1;
        for(int j=1;j<=19&&j<=n;++j) {
            if(!f[i][j])  break;
            for(int k=i*2;k<=m;k+=i)  f[k][j+1]=(f[k][j+1]+f[i][j])%mod;
            ll x=calc(m/i,n-j);
            ans=(ans+f[i][j]*x%mod*(n-j)%mod)%mod;
        }
    }
    printf("%lld",ans);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T=1;
    // cin>>T;
    while(T--) {
        solve();
    }
    return 0;
}

减的思路

题解和我开始想那种有点类似,是减每个数能被删的次数

把队长那个和我想的结合下差不多

按题解的思路:

  • 先固定一个元素 $x$,如果他能被删掉:
    • 比他小的,构成了一个以 $x$ 结尾的倍数链
    • 比他大的,都是 $x$ 的倍数
  • 小的部分枚举倍数链,求得长度为 c,当前值为 x 的链的数量
  • 大的部分直接组合数求

最后减去的总数为 $\sum_{1\leq c\leq n,1\leq x\leq m}f_{c,x}\cdot \begin{pmatrix} \lfloor m/x\rfloor-1 \ n-c \end{pmatrix}.$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <bits/stdc++.h>
using namespace std;
#define ls now<<1
#define rs now<<1|1
#define endl "\n"
#define lowbit(x) ((x)&(-x))
typedef long long ll;
const int N=1e5+7, mod=998244353;

int m,n;
ll ans,inv[N],inc[N];
ll f[25][N];

// 快速幂
ll qpow(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

// inc存的是阶乘,inv存的是阶乘的逆元
void init(){
    inc[0]=1;
    for(int i=1;i<N;i++){
        inc[i]=inc[i-1]*i%mod;
    }
    inv[N-1]=qpow(inc[N-1],mod-2);
    for(int i=N-2;i>0;i--){
        inv[i]=inv[i+1]*(i+1)%mod;
    }
}

// 计算组合数
ll C(int a,int b){
    if(b==0||a==b)return 1;
    if(a<b)return 0;
    return inc[a]*inv[a-b]%mod*inv[b]%mod;
}

void solve(){
    cin>>m>>n;
    ans=C(m,n)*n%mod;
    for(int i=0;i<=m;i++)f[1][i]=1;
    for(int i=2;i<=20;i++){
        for(int j=1;j<=m;j++){
            for(int k=2;k*j<=m;k++){
                f[i][k*j]=(f[i][k*j]+f[i-1][j])%mod;
            }
        }
    }
    for(int i=1;i<=20;i++){
        for(int j=1;j<=m;j++){
            if(n-i>=0){
                ll k=C((m/j)-1,n-i);
                if((m/j)-1>=0)ans=(ans-(f[i][j]*C((m/j)-1,n-i))%mod+mod)%mod;
                else ans=(mod+ans-f[i][j])%mod;
            }
        }
    }
    cout<<ans<<endl;
}

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

I. The Hanged Man

赛时想的是,固定以 1 为根,然后往下 dfs,优先连最小的环,看最后剩下的是否能成环即可

但是交上去 WA 了,讨论发现,如果 1 的子节点有奇数个,最后会有一个子节点的边无法连全

队长说可以拆树,把一个有奇数子节点的拆出去,其他的从 1 往下连,这个拆出去的就先把能连的连了,最后会剩下一条边,用这个边结合到 1 的子树中即可

写完调了半天,发现是没赋初值的问题。。。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+7,mod=998244353;
vector<int> nx[N];
int deg[N],fa[N],son[N],a[N],dep[N],n,siz[N];
vector<pair<int,int>> ans;
bool vis[N];
void dfs(int x,int FA)
{
    vis[x]=1;siz[x]=1;
    fa[x]=FA;dep[x]=dep[FA]+1;
    for(auto y:nx[x]) {
        if(y!=FA)  dfs(y,x),++son[x],siz[x]+=siz[y];
    }
}
void sol(int rt)
{
    deque<int> q;
    dfs(rt,0);
    for(int i=1;i<=n;++i)
        if(vis[i]&&son[i]==0&&i!=rt)  q.push_back(i);
    int sum=0;
    while(!q.empty()&&sum!=siz[rt]-1) {
        int x=q.front();
        q.pop_front();
        int now=x;
        while(now!=rt&&son[now]<=1)  now=fa[now];
        if(!a[now])  a[now]=x;
        else {
            int cnt=dep[x]+dep[a[now]]-2*dep[now];
            ans.push_back(make_pair(x,a[now]));
            a[now]=0;
            sum+=cnt;
            son[now]-=2;
            deg[now]-=2;
            if(deg[now]==1&&now!=rt)  q.push_back(now);
        }
    }
}
void solve()
{
    ans.clear();
    int rt=0;
    cin>>n;
    for(int i=1;i<=n;++i)  nx[i].clear(),vis[i]=0,siz[i]=dep[i]=deg[i]=son[i]=fa[i]=a[i]=0;
    for(int i=1;i<n;++i) {
        int x,y;
        cin>>x>>y;
        nx[x].push_back(y);
        nx[y].push_back(x);
        ++deg[x];++deg[y];
    }
    if(n==2) {
        printf("-1\n");
        for(int i=1;i<=n;++i)  nx[i].clear(),vis[i]=siz[i]=dep[i]=deg[i]=son[i]=fa[i]=a[i]=0;
        return;
    }
    for(int i=1;i<=n;++i) {
        if(!rt&&deg[i]%2==0)  rt=i;
    }
    if(!rt) {
        int flag=0;
        for(int i=1;i<=n;++i) {
            for(auto x:nx[i]) {
                if((deg[x]-1)%2==1) {
                    flag=x;
                    break;
                }
            }
            if(flag)  rt=i;
        }
        if(!flag) {
            printf("-1\n");
            for(int i=1;i<=n;++i)  nx[i].clear(),vis[i]=siz[i]=dep[i]=deg[i]=son[i]=fa[i]=a[i]=0;
            return;
        }
        nx[rt].erase(find(nx[rt].begin(),nx[rt].end(),flag));
        nx[flag].erase(find(nx[flag].begin(),nx[flag].end(),rt));
        --deg[rt];--deg[flag];
        sol(rt);sol(flag);
        ans.push_back(make_pair(rt,a[flag]));
        printf("%d\n",ans.size());
        for(auto x:ans)  printf("%d %d\n",x.first,x.second);
    } else {
        sol(rt);
        printf("%d\n",ans.size());
        for(auto x:ans)  printf("%d %d\n",x.first,x.second);
        
    }
    for(int i=1;i<=n;++i)  nx[i].clear(),vis[i]=siz[i]=dep[i]=deg[i]=son[i]=fa[i]=a[i]=0;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T=1;
    cin>>T;
    while(T--) {
        solve();
    }
    return 0;
}

赛后看题解,这实现方法差的也太多了,我们那干了 100 行,这少了接近一半

可以发现:

  • 如果一个节点他的子节点有偶数个,那就可以互相连
  • 如果一个节点他的子节点有奇数个,那就任选一个不连,用不连的这个替换父节点在上一层树中的位置即可

只要找一个度数为偶数的节点为根,这个方案总能实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <bits/stdc++.h>
using namespace std;
#define ls now<<1
#define rs now<<1|1
#define endl "\n"
#define lowbit(x) ((x)&(-x))
typedef long long ll;
const int N=3e5+7, mod=1e9+7;

int n;
vector<int> dep[N];
vector<pair<int,int>>ans;
bool vis[N];

int dfs(int now){
    vis[now]=1;
    deque<int>tmp;
    for(auto x:dep[now]){
        if(!vis[x]){
            int flag=dfs(x);
            if(!flag)tmp.push_back(x);
            else tmp.push_back(flag);
        }
    }
    if(tmp.size()==0)return 0;
    while(tmp.size()>=2){
        int x=tmp.front();
        tmp.pop_front();
        int y=tmp.front();
        tmp.pop_front();
        ans.push_back({x,y});
    }
    if(tmp.size())return *tmp.begin();
    else return 0;
}

void solve(){
    cin>>n;
    ans.clear();
    for(int i=1;i<=n;i++)dep[i].clear(),vis[i]=0;
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        dep[u].push_back(v);
        dep[v].push_back(u);
    }
    bool flag=0;
    int pos=1;
    for(int i=1;i<=n;i++){
        if((int)(dep[i].size())%2==0){
            flag=1,pos=i;
            break;
        }
    }
    if(!flag){
        cout<<"-1\n";
        return;
    }
    dfs(pos);
    cout<<ans.size()<<endl;
    for(auto [x,y]:ans)cout<<x<<" "<<y<<endl;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)solve();
    return 0;
}
Licensed under CC BY-NC-SA 4.0
最后更新于 Sep 11, 2025 16:27 +0800
发表了95篇文章 · 总计15万2千字
永远相信美好的事情即将发生。
使用 Hugo 构建
主题 StackJimmy 设计