【学习笔记】ARC158

显然按位考虑。我们只需要数进位的总次数即可。只要两个数之和 ≥ 1 0 i \ge 10^{i} 10i就会进位,而这只需要排一个序然后用指针扫一遍即可。

说实话还是挺脑洞的。~~但是这个随机化的思想总还是应该想到吧?~~

这题微妙的地方在于左右两边是齐次的,并且次数恰好相差 1 1 1。也就是说,任取三个数 a , b , c a,b,c a,b,c,把等式左右两边都看成函数值的话,考虑再取 t a , t b , t c ta,tb,tc ta,tb,tc,因为是等式所以两边可以同时约掉 t 3 n t^{3n} t3n,那么相当于左边的式子乘了一个数 t t t,这道题就做完了。

#include<bits/stdc++.h>
#define pb push_back
#define ll long long
using namespace std;
int T;
ll n,mod;
ll fpow(ll x,ll y){
    ll z(1);
    for(;y;y>>=1){
        if(y&1)z=z*x%mod;
        x=x*x%mod;
    }return z;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>T;mt19937 t(114514);
    while(T--){
        cin>>n>>mod;
        ll x=-1,y=-1,z=-1;
        for(int i=1;i<=100;i++){
            ll a=t()%(mod-1)+1,b=t()%(mod-1)+1,c=t()%(mod-1)+1;
            if(a==b||a==c||b==c)continue;
            ll l=(a+b+c)*(fpow(a,n)+fpow(b,n)+fpow(c,n))%mod*(fpow(a,2*n)+fpow(b,2*n)+fpow(c,2*n))%mod;
            ll r=(fpow(a,3*n)+fpow(b,3*n)+fpow(c,3*n))%mod;
            if(l&&r){
                ll t=r*fpow(l,mod-2)%mod;
                x=a*t%mod,y=b*t%mod,z=c*t%mod;
                break;
            }
        }
        ll le=(x+y+z)*(fpow(x,n)+fpow(y,n)+fpow(z,n))%mod*(fpow(x,2*n)+fpow(y,2*n)+fpow(z,2*n))%mod;
        ll ri=(fpow(x,3*n)+fpow(y,3*n)+fpow(z,3*n))%mod;
        assert(le==ri);
        assert(~x);
        ll a[3]={x,y,z};
        sort(a,a+3);
        cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<"\n";
    }
}

首先分治。然后考虑跨区间的贡献,设每个位置走到中间近的那个端点为 1 1 1,远的那个为 0 0 0

我们的目的是大力分讨,最后搞出来一个类似多维偏序的问题。~~这也非常符合第一直觉~~

注意到这里其实本质上只有两种情况,那么我们枚举在上端还是下端拼接,并且固定左边的点 X X X,设其对应的代价为 P ( X ) , Q ( X ) P(X),Q(X) P(X),Q(X),那么我们需要对于所有满足 P ( X ) + P ( Y ) < Q ( X ) + Q ( Y ) P(X)+P(Y)<Q(X)+Q(Y) P(X)+P(Y)<Q(X)+Q(Y)的点求和,移项得到 Q ( Y ) − P ( Y ) > P ( X ) − Q ( X ) Q(Y)-P(Y)>P(X)-Q(X) Q(Y)P(Y)>P(X)Q(X),用指针扫一遍就能得到答案。

#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
#define fi first
#define se second
using namespace std;
const int N=2e5+5;
const int mod=998244353;
int n;
ll a[N][2],dp[N][2][2],sum[N],sum2[N],res;
void add(ll &x,ll y){
    x=(x+y)%mod;
}
vector<pair<ll,ll>>vec1,vec2;
bool cmp1(pair<ll,ll>x,pair<ll,ll>y){
    return x.fi-x.se<y.fi-y.se;
}
bool cmp2(pair<ll,ll>x,pair<ll,ll>y){
    return x.se-x.fi<y.se-y.fi;
}
void solve(int l,int r){
    if(l==r){
        add(res,3*(a[l][0]+a[l][1]));
        return;
    }
    int mid=l+r>>1;
    solve(l,mid),solve(mid+1,r);
    vec1.clear(),vec2.clear();
    dp[mid][0][0]=a[mid][0],dp[mid][0][1]=a[mid][0]+a[mid][1];
    dp[mid][1][0]=a[mid][0]+a[mid][1],dp[mid][1][1]=a[mid][1];
    for(int i=mid-1;i>=l;i--){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                dp[i][j][k]=dp[i+1][j][k]+a[i][j];
            }
        }
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                dp[i][j][k]=min(dp[i][j][k],dp[i][j^1][k]+a[i][j]);
            }
        }
    }
    for(int i=l;i<=mid;i++){
        for(int j=0;j<2;j++){
            vec1.pb({dp[i][j][0],dp[i][j][1]});
        }
    }
    dp[mid+1][0][0]=a[mid+1][0],dp[mid+1][0][1]=a[mid+1][0]+a[mid+1][1];
    dp[mid+1][1][0]=a[mid+1][0]+a[mid+1][1],dp[mid+1][1][1]=a[mid+1][1];
    for(int i=mid+2;i<=r;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                dp[i][j][k]=dp[i-1][j][k]+a[i][j];
            }
        }
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                dp[i][j][k]=min(dp[i][j][k],dp[i][j^1][k]+a[i][j]);
            }
        }
    } 
    for(int i=mid+1;i<=r;i++){
        for(int j=0;j<2;j++){
            vec2.pb({dp[i][j][0],dp[i][j][1]});
        }
    }
    sort(vec1.begin(),vec1.end(),cmp1);
    sort(vec2.begin(),vec2.end(),cmp2);
    int j=0;int l1=vec1.size(),l2=vec2.size();
    for(int i=0;i<l2;i++){
        sum[i+1]=(sum[i]+vec2[i].fi)%mod;
        sum2[i+1]=(sum2[i]+vec2[i].se)%mod;
    }
    for(int i=0;i<l1;i++){
        while(j!=l2&&vec2[j].se-vec2[j].fi<=vec1[i].fi-vec1[i].se){
            j++;
        }
        add(res,2*vec1[i].fi%mod*(l2-j));
        add(res,2*(sum[l2]-sum[j]));
        add(res,2*vec1[i].se%mod*j);
        add(res,2*sum2[j]);
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int j=0;j<2;j++){
        for(int i=1;i<=n;i++){
            cin>>a[i][j];
        }
    }
    solve(1,n);
    cout<<res;
}

考虑 n = 2 n=2 n=2的情形。比较简单的情况是最后操作的数位上的数字不相同,这样我们只要看最后一次操作即可。否则就看之前的第一次产生影响的操作。换句话说,这次操作对这对数不产生影响。

于是我们有了最基础的推论,对于操作序列 X X X,我们只要保留 0 ∼ K − 1 0\sim K-1 0K1的最后一次操作即可。

然后考虑从后往前状压。在此之前,我们需要对限制进行一些转化。~~没办法,这道题就是几个 d p dp dp拼起来,也没办法跳步~~假设当 k ∈ S 1 k\in S_1 kS1 A i A_i Ai A j A_j Aj之前, k ∈ S 2 k\in S_2 kS2 A i A_i Ai A j A_j Aj之后, k ∈ S 3 k\in S_3 kS3时没有影响。如果最终序列交换了位置,那么第一个不在 S 3 S_3 S3中的数应该在 S 2 S_2 S2中,否则一定在 S 1 S_1 S1中,对于给定的 S 3 S_3 S3可以求出所有限制的集合的交,这样就完成了等价的转化。注意我们只需要保证 B i , B i + 1 B_i,B_{i+1} Bi,Bi+1的相对位置关系即可,因此这样的限制事实上只有 n n n对。

那么对于一个合法的 Y Y Y(只保留最后一次操作),有多少 X X X与之对应呢?可以通过打表 ~~或者非常运气的尝试~~ 得出答案是 S u r j ( M , n ) n ! \frac{Surj(M,n)}{n!} n!Surj(M,n),其中 S u r j ( M , n ) Surj(M,n) Surj(M,n)表示 M M M个数组成的集合大小为 n n n的方案数, n n n表示 Y Y Y序列的长度。因为 n ≤ 18 n\le 18 n18所以很好算就不说了。~~其实也非常常规~~

状压的地方可能要自己编一下做法。不过搞一下发现就是一个求子集和的形式就很愉快。

复杂度 O ( K 2 2 K ) O(K^22^K) O(K22K)。应该有更好的预处理方法,留给读者思考。~~其实是我不会~~

#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
#define fi first
#define se second
using namespace std;
const int N=2e5+5;
const int mod=998244353;
int n,m,K,id[N];
int dp[18][1<<18],vis[1<<18];
ll f[1<<18],res[20],fac[20],inv[20],ans;
ll a[N],b[N];
ll F[20];
map<int,int>id1;
map<pair<int,int>,int>id2;
void add(ll &x,ll y){
    x=(x+y)%mod;
}
ll fpow(ll x,ll y){
    ll z(1);
    for(;y;y>>=1){
        if(y&1)z=z*x%mod;
        x=x*x%mod;
    }
    return z;
}
ll binom(ll x,ll y){
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
void init(int n){
    fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    inv[n]=fpow(fac[n],mod-2);
    for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
}
int get(ll x,int y){
    return x/F[y]%10;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>K;init(K);F[0]=1;for(int i=1;i<=K;i++)F[i]=F[i-1]*10;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++){
        id1[a[i]]++;
        id2[{a[i],id1[a[i]]}]=i;
    }
    id1.clear();
    for(int i=1;i<=n;i++){
        id1[b[i]]++;
        id[i]=id2[{b[i],id1[b[i]]}];
    }
    for(int i=0;i<K;i++){
        for(int j=0;j<1<<K;j++){
            dp[i][j]=(1<<K)-1;
        }
    }
    for(int i=0;i<1<<K;i++)vis[i]=1;
    for(int i=1;i<n;i++){
        int s=0,s1=0;
        for(int j=0;j<K;j++){
            if(get(b[i],j)!=get(b[i+1],j)){
                s+=1<<j;
                if(get(b[i],j)<get(b[i+1],j)){
                    s1+=1<<j;
                }
            }
        }
        for(int j=0;j<K;j++){
            if(get(b[i],j)!=get(b[i+1],j)){
                assert(s&(1<<j));
                dp[j][s-(1<<j)]&=s1;
            }
        }
        if(id[i]>id[i+1]){
            vis[s]=0;
        }
    }
    for(int i=0;i<K;i++){
        for(int j=0;j<1<<K;j++){
            if(!(j>>i&1)){
                vis[j+(1<<i)]&=vis[j];
            }
        }
    }
    for(int i=0;i<K;i++){
        for(int j=0;j<K;j++){
            for(int k=0;k<1<<K;k++){
                if(!(k>>j&1)){
                    dp[i][k+(1<<j)]&=dp[i][k];
                }
            }
        }
    }
    f[0]=1;
    for(int i=0;i<1<<K;i++){
        for(int j=0;j<K;j++){
            if(!(i>>j&1)&&(dp[j][(1<<K)-1-i-(1<<j)]&(1<<j))){
                add(f[i+(1<<j)],f[i]);
            }
        }
    }
    for(int i=1;i<=K;i++){
        res[i]=fpow(i,m);
        for(int j=1;j<i;j++){
            add(res[i],-res[j]*binom(i,j));
        }
    }
    for(int i=0;i<1<<K;i++){
        if(!vis[(1<<K)-1-i])continue;
        int cnt=0;
        for(int j=0;j<K;j++)if(i>>j&1)cnt++;
        add(ans,f[i]*res[cnt]%mod*inv[cnt]);
    }
    cout<<(ans+mod)%mod;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值