[训练记录]2021icpc上海

D. Strange Fractions

ab=x

原式即

x+1x=pq 解方程然后δ为整数即可算出对应的ab

复制代码
#include <bits/stdc++.h>
using namespace std;
long long p,q;
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        cin>>p>>q;
        if (p<2*q){
        printf("0 0\n");
        continue;
        }
        long long delta = sqrt(1ll*p*p-4ll*q*q);
        if (delta*delta == 1ll*p*p-4ll*q*q){
            long long b = p+delta;
            long long a = 2*q;
            long long r = __gcd(a,b);
            printf("%lld %lld\n",a/r,b/r);
        }
        else{
            printf("0 0\n");
        }
    }
    return 0;
}
复制代码

E. Strange Integers

萌萌签到题

排序后贪心,能取则取即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
int a[100005],N,K;
int main(){
    scanf("%d%d",&N,&K);
    for (int i = 1; i<= N ;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+N+1);
    int nw = 0,ans = 0;
    for (int i=2;i<=N;i++){
        //cout<<nw<<" ";
        nw += a[i]-a[i-1];
        //cout<<a[i]<<" "<<nw<<endl;
        if (nw >= K){
            ans ++;
            nw = 0;
        }
    }
    cout<<ans+1;
    return 0;
}
复制代码

G. Edge Groups

树形dp

f[i]表示i的子树能分尽量分的方案

如果子树的节点个数是奇数的话

说明子树内就可以自己分配完,不用这条接到父亲的边

否则一定要用掉这条接到父亲的边

然后发现,这些不用接到父亲的边可以称为"自由边"

可以随意配对

g[n]表示n个东西随意配对的方案数

g[n]=g[n2](n1)递推即可。

如果自由边数量为奇数,说明有一条自由边要向上配对

cnt+1即可

复制代码
#include <bits/stdc++.h>
using namespace std;
vector<int> G[100005];
long long dp[100005],f[100005],Siz[100005]; 
const int fish = 998244353;
void jt(int u,int v){
    G[u].push_back(v);
    G[v].push_back(u);
}
void dfs(int Now,int fa){
    dp[Now] = 1;
    Siz[Now] = 1;
    int cnt = 0;
    for (auto v:G[Now]){
        if (v == fa) continue;
        dfs(v,Now);
        (dp[Now] *= dp[v])%=fish;
        Siz[Now] += Siz[v];
        if (Siz[v]&1) cnt++; 
    }    if (cnt & 1) cnt++;
    (dp[Now] *= f[cnt])%=fish;
}
int main(){
    int N;
    scanf("%d",&N);
    for (int i = 1 ; i < N; i++){
        int u,v;
        scanf("%d%d",&u,&v);
        jt(u,v);
    }
    f[0] = 1;
    for (int i = 2 ; i<= N;i+=2)
        f[i] = 1ll*f[i-2]*1ll*(i-1)%fish;
    dfs(1,1);
    cout<<dp[1];
    return 0;
}
复制代码

H. Life is a Game

有点像那题归程。

发现因为走边没有消耗,所以其实走路的过程在最小生成树上一定是最优的。

但是有多个询问,不好处理

利用krusual重构树,建树出来

因为越接近根节点的边的值越大,所以其实只要不断地往上跳,能跳到一个点就说明能到这个点的所有子节点,更新一下当前的能量

这个跳的过程可以用倍增优化。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M,Q;
int a[200005];
long long Sum[200005],Cos[200005];
int fa[200005];
int f[200005][25];
vector<int>G[200005]; 
void AddEdge(int u,int v){
    G[u].push_back(v);
}
struct Node{
    int u,v,w;
}E[200005];
int temp(Node a,Node b){
    if (a.w == b.w && a.u == b.v) return a.v<b.v;
    if (a.w == b.w) return a.u<b.u;
    return a.w<b.w;
}
int Getfa(int x){
    if (x == fa[x]) return x;
    else return fa[x] = Getfa(fa[x]);
}
int tot;
void Krusual(){
    scanf("%d%d%d",&N,&M,&Q);
    for (int i = 1 ; i <= N ; i++) fa[i] = i;
    for (int i = 1;i<=N;i++) scanf("%d",&a[i]);
    for (int i = 1;i<=M;i++) scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
    sort(E+1,E+M+1,temp);
    tot = N;
    for (int i = 1 ;i<= M ;i++){
        if (Getfa(E[i].u) != Getfa(E[i].v)){
            int fx = Getfa(E[i].u),fy=Getfa(E[i].v);
            tot++;
            Cos[tot] = E[i].w;
            AddEdge(tot,fx);
            AddEdge(tot,fy);
            AddEdge(fx,tot);
            AddEdge(fy,tot);
            fa[fx] = fa[fy] = fa[tot] = tot; 
        }
    }
}
void dfs(int Now,int fa){
    f[Now][0] = fa;
    Sum[Now] = a[Now];
    for (int i = 1;i <= 20 ; i++) f[Now][i] = f[f[Now][i-1]][i-1];
    for (auto x : G[Now]){
        if (x == fa) continue;
        dfs(x,Now);
        Sum[Now] += Sum[x];
    }
}
void Solve(int x,int K){
    Cos[0] = 1e16;
    long long Now = K + a[x];
    int pos = x;
    while (pos != tot){
        bool flag = false;
        for (int i = 20 ; i >= 0 ;i--)
            if (Now >= Cos[f[pos][i]]){
                pos = f[pos][i];
                flag = true;
        }
        if (flag){
            Now = K + Sum[pos];
        }else break;
    }
    printf("%lld\n",Now);
}
int main(){
    Krusual();
    dfs(tot,tot);
    while (Q--){
        int x,K;
        scanf("%d%d",&x,&K);
        Solve(x,K);
    }
    return 0;
}
复制代码

I. Steadily Growing Steam

首先理解清楚题意。

其实本质上是一个先选物品后分组的问题

容易想到一个朴素的dp

dp[i][j][S][T]表示考虑到i,翻倍次数为j,S集合的重量和为S,T集合重量和为T的最优解

但是会发现这样时间和空间都不能接受,需要压维

首先第一维可以滚动数组优化一下

然后考虑ST,其实我们需要知道的信息有什么?

其实只有ST是否相等,而不关心他们的具体取值、

于是其实后两维可以压成一维ST(很常见的套路)

然后就根据每个物品放不放,翻倍不翻倍,放S还是T分类即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
 
const int N = 105;
const long long NINF = 0x8888888888888888;
 
int n, k;
long long dp[2][N][N * 13 * 2]; // dp[i][j][S - T]
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> k;
    int o = 0;
    memset(dp, NINF, sizeof dp);
    dp[o ^ 1][0][0] = 0;
    for (int i = 1; i <= n; i++, o ^= 1) {
        long long v; int t;
        cin >> v >> t;
        for (int j = 0; j <= k; j++) {
            for (int st = 0; st <= 100 * 13 * 2; st++) {
                dp[o][j][st] = dp[o ^ 1][j][st];
                if (dp[o ^ 1][j][abs(st - t)] != NINF) {
                    dp[o][j][st] = max(dp[o][j][st], dp[o ^ 1][j][abs(st - t)] + v);
                }
                if (dp[o ^ 1][j][st + t] != NINF) {
                    dp[o][j][st] = max(dp[o][j][st], dp[o ^ 1][j][st + t] + v);
                }
                if (j > 0 && dp[o ^ 1][j - 1][abs(st - t * 2)] != NINF) {
                    dp[o][j][st] = max(dp[o][j][st], dp[o ^ 1][j - 1][abs(st - t * 2)] + v);
                }
                if (j > 0 && dp[o ^ 1][j - 1][st + t * 2] != NINF) {
                    dp[o][j][st] = max(dp[o][j][st], dp[o ^ 1][j - 1][st + t * 2] + v);
                }
            }
        }
//        for (int j = 0; j <= k; j++) {
//            for (int st = 0; st <= 4; st++) {
//                cout << dp[o][j][st] << " ";
//            } 
//            cout << endl;
//        }
    }
    o ^= 1;
    long long ans = NINF;
    for (int j = 0; j <= k; j++) {
        ans = max(ans, dp[o][j][0]);
    }
    cout << ans << endl;
    return 0;
}
复制代码

K. Circle of Life

训练赛最后一分钟压线通过,十分的刺激

很有趣的一个题

首先,考虑题目给的一个循环节,发现循环节可以循环,有一部分原因是因为两边是墙壁,会直接炸掉1

于是就会猜想,是否可以把这个字符串分解成一堆比较小的串,这些串形如1xxxxxx1,然后在若干次操作后回到1xxxxx1,此时首尾的两个1就充当原本题目中墙壁的位置。

然后稍微试一试就会发现1001是一个满足条件的串

但是至此我们只解决了长度模40的部分,还有模41,2,3的部分

显然,长度为2有唯一解01

011001拼在一起,会发现这个串仍然有循环。

所以2的做法是,01100110011001以此类推

然后到3,会发现长度为3是无解的(可以手动验证一下)

问题即转化为找一个长度为7的串,使得它合法,且最后一位为1

人类智慧或者打表可以发现,长度为7的串答案是0101001

010100110011001这样即可。

还有一个长度模41,显然这时候1是无解的

考虑长度为5的串,发现答案是10001

同样的构造即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
string ss[7]={"1001","10001","01","0101001"};
int main(){
    int N;
    cin>>N;
    if (N<=7){
        if (N==2) {
            cout<<"01"; 
        }
        if (N==3){
            cout<<"Unlucky";
        }
        if (N==4){
            cout<<"1001";
        }
        if (N==5){
            cout<<"10001";
        }
        if (N==6){
            cout<<"011001";
        }
        if (N==7){
            cout<<"0101001";
        }
    }
    else{
        int x = N%4;
        cout<<ss[x];
        N-=ss[x].length();
        int cnt = N/4;
        for (int i = 1;i<=cnt;i++){
            cout<<"1001";
        }
    }
    return 0;
}
复制代码

尝试了但还没做出来的题:

J. Two Binary Strings Problem

只是记录一下训练赛时的想法。

首先,这个问题可以类似括号匹配的,0视为1,1视为+1,区间和大于0意味着这个区间合法。

此时,假设我们已经通过某种手段快速求得了对于每个Ai,它长度为1,2,3,...k是否合法,存在一个bitset

然后考虑怎么快速的获得luckynumber

对于bi1的位置来说,显然,所有的数字在这一位都要是1,那么全部的k取个&,看是不是1即可。

同理,bi0的位置,其实此时0充当了1的位置,对这个串取个反即可。

这样做完之后直接把所有的位全部&起来就好了。

回到最开始的问题,如何快速求解当前位置对应的1区间

考虑问题的本质

1、位置在当前位置前面

2、前缀和小于当前位置

显然,这是一个二维偏序问题。

sum排个序,顺序扫描,前缀位置做个bitset,每次扫到一个位置的时候,把前面的答案拿进来,去掉位置比当前靠后的,然后再把这个位置本身加入前缀的bitset

但是注意一个细节,这里的是绝对位置关系,而上面的串存的是相对位置关系。

这个问题位移取反应该能解决?
训练赛中代码没弄完,细节挺多的,回头应该会补(咕咕?)

 

赛后补题:

B

把题意稍微转化一下

把不能填入的关系转成图上的边,(x,Px)表示x不能与Px相邻

正面考虑很困难,反面考虑。

发现因为一个点只能连一条边出去,所以这个图一定会是一堆环构成的。

然后就会发现这个东西:

在一个环上选K组关系的答案是CNK

写一下环的生成函数,然后用分治ntt合并一下环的答案

最后计算答案的时候枚举一下有几组不合法的关系,容斥即可。

像多校某场中的一个题,题意基本是相同的,只是函数不太一样。

复制代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<iostream>
#define ll long long
using namespace std;
const ll T=30,P=998244353;
const long long fish=998244353;
int N,M;
long long fac[200005],inv[200005];
long long Pow(long long x,long long y){
    long long ans=1;
    for (;y;y>>=1,x=1ll*x*1ll*x%fish)
        if (y&1) ans=1ll*ans*1ll*x%fish;
    return ans;
}
void Pre(){
    fac[0]=1;
    for (int i=1;i<=200000;i++)
        fac[i]=1ll*fac[i-1]*1ll*i%fish;
    inv[200000]=Pow(fac[200000],fish-2);
    for (int i=199999;i>=0;i--){
        inv[i]=1ll*inv[i+1]*1ll*(i+1)%fish;
    }
}
long long C(long long n,long long r){
    if (n<0 || r<0 || n-r<0) return 0;
    return 1ll*fac[n]*1ll*inv[r]%fish*1ll*inv[n-r]%fish;
}
struct Poly{
    ll a[800001],n;
}F[T];
ll n,m,r[800001],x[800001],y[800001];
bool v[T];
ll power(ll x,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*x%P;
        x=x*x%P;b>>=1;
    }
    return ans;
}
void NTT(ll *f,ll n,ll op){
    for(ll i=0;i<n;i++)
        if(i<r[i])swap(f[i],f[r[i]]);
    for(ll p=2;p<=n;p<<=1){
        ll tmp=power(3,(P-1)/p),len=p>>1;
        if(op==-1)tmp=power(tmp,P-2);
        for(ll k=0;k<n;k+=p){
            ll buf=1;
            for(ll i=k;i<k+len;i++){
                ll tt=buf*f[i+len]%P;
                f[i+len]=(f[i]-tt+P)%P;
                f[i]=(f[i]+tt)%P;
                buf=buf*tmp%P;
            }
        }
    }
    if(op==-1){
        ll invn=power(n,P-2);
        for(ll i=0;i<n;i++)
            f[i]=f[i]*invn%P;
    }
    return;
}
void Mul(Poly &F,Poly &G){
    ll n=1;
    while(n<F.n+G.n)n<<=1;
    for(ll i=0;i<F.n;i++)x[i]=F.a[i];
    for(ll i=0;i<G.n;i++)y[i]=G.a[i];
    for(ll i=F.n;i<n;i++)x[i]=0;
    for(ll i=G.n;i<n;i++)y[i]=0;
    for(ll i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)?(n>>1):0);
    NTT(x,n,1);NTT(y,n,1);
    for(ll i=0;i<n;i++)x[i]=x[i]*y[i]%P;
    NTT(x,n,-1);
    for(ll i=0;i<n;i++)F.a[i]=x[i];
    F.n=F.n+G.n-1;return; 
}
ll Find(){
    for(ll i=0;i<T;i++)
        if(!v[i]){v[i]=1;return i;}
}
int Siz[200010],Size;
bool vis[200010];
ll Solve(ll l,ll r){
    if(l==r){
        ll p=Find();
        F[p].a[0]=1;
        long long NN = Siz[l];
        for(ll i=0;i<Siz[l];i++){
        F[p].a[i]=C(NN,i);
        F[p].n=Siz[l];
        }
        return p;
    }
    ll mid=(l+r)>>1;
    ll ls=Solve(l,mid),rs=Solve(mid+1,r);
    Mul(F[ls],F[rs]);v[rs]=0;
    return ls;
}
int PP[200010];
signed main()
{
    int T;
    Pre();
    int K;
    
    scanf("%d",&N);
    Size=0;
    for (int i=1;i<=N;i++){
        scanf("%d",&PP[i]);
        vis[i]=0;
        Siz[i] = 0;
    }
    for (int i=1;i<=N;i++)
        if (!vis[i]){
            Size++;
            int cntt=0;
            int x=i;
            while (!vis[x]){
                cntt++;
                vis[x]=true;
                x=PP[x];
            }
            Siz[Size] = cntt;
    }
    ll p=Solve(1,Size);
    long long ans = 0;
    for (int i = 0 ; i <= N ;i++){
        if (i&1) ((ans -= 1ll*fac[N-i] * 1ll*F[p].a[i]%fish)+=fish)%=fish;
        else (ans += 1ll*fac[N-i]*1ll*F[p].a[i]%fish)%=fish;
    }
    cout<<ans<<endl;
    return 0;
}
/*
2 3 4 1 6 7 5 9 10 11 8
*/
复制代码

 

posted @   si_nian  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
欢迎阅读『[训练记录]2021icpc上海』
点击右上角即可分享
微信分享提示