CSP膜你赛26

T1 字符交换

可以枚举每个字符作为中心位置的情况,
然后贪心地把相同的字符拽到这个中心位置
求出在限制内的最大值,即最终解
我的做法是:定义dp[i]为达到i的最小交换次数
对于26个字符分别考虑
对于相同字符把它们尽量向中间的那个换一定最优
长度为偶数的中间两个字符都可能作为最优方案
而长度为奇数的话一定都换到中间那个字符的左右一定最优
于是可以枚举m值的大小,再枚举一下起点
那么我们如何做到\(O(1)\)求这个最小的交换次数
用前缀和维护一下位置的和即可
时间复杂度:\(O(n^2)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#define rint register int
using namespace std;

const int maxn=4005;
int pos[30][maxn],cnt[30],dp[maxn],sum[30][maxn];
char s[maxn];

int Find(rint id,rint l,rint r){
    int len=r-l+1;
    int suml,sumr;
    int ans,mid=(l+r)/2;
    int midlen=(len-1)/2;
    if(len&1){
        suml=pos[id][mid]*midlen-(sum[id][mid-1]-sum[id][l-1])-(1+midlen)*midlen/2;
        sumr=(sum[id][r]-sum[id][mid])-pos[id][mid]*midlen-(1+midlen)*midlen/2;
        return suml+sumr;
    }else{
        suml=pos[id][mid]*(mid-l)-(sum[id][mid-1]-sum[id][l-1])-(1+mid-l)*(mid-l)/2;
        sumr=(sum[id][r]-sum[id][mid])-pos[id][mid]*(r-mid)-(1+r-mid)*(r-mid)/2;
        ans=suml+sumr;
        mid++;
        suml=pos[id][mid]*(mid-l)-(sum[id][mid-1]-sum[id][l-1])-(1+mid-l)*(mid-l)/2;
        sumr=(sum[id][r]-sum[id][mid])-pos[id][mid]*(r-mid)-(1+r-mid)*(r-mid)/2;
        ans=min(ans,suml+sumr);
        return ans;
    }
}

int main(){
    memset(dp,0x3f,sizeof(dp));
    dp[1]=0;
    int n,m;scanf("%d%d",&n,&m);
    scanf("%s",s+1);
    for(rint i=1;i<=n;i++){
        int id=s[i]-'a'+1;
        pos[id][++cnt[id]]=i;
        sum[id][cnt[id]]=sum[id][cnt[id]-1]+i;
    }
    for(rint js=1;js<=26;js++){
        for(rint i=2;i<=cnt[js];i++){
            for(rint j=1;j<=cnt[js]-i+1;j++){
                int l=j,r=j+i-1;
                dp[i]=min(dp[i],Find(js,l,r));
            }
        }
    }
    int ans=0;
    for(rint i=1;i<=n;i++) if(dp[i]<=m) ans=i;
    printf("%d\n",ans);
    return 0;
}

T2 平方数

一个数要想与其他数成为平方数
把这个数质因子分解后,
这个数本身的所有平方因子都是没有意义的
就可以把这些平方因子都筛掉,只保留奇数次幂的质因子
开个桶记录这个值,就可以了
但是直接这样做的复杂度并不优秀:\(O(n\sqrt n)\)。跟暴力一个分
再优化一下的话可以筛出\(\sqrt n\)以内的所有质数,大概有3000个,可以拿到70的好成绩
正解的思路更妙:我们只筛掉1000以内的所有质数
剩下的那个数最多只含有两个质因子的,因为我们已经筛完了所有1e3以下的质因子
若有3个及以上大于1000的质因子就爆掉1e9的范围了
只要判断一下剩下的数是否是个平方数就好了

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <map>
#define rint register int
using namespace std;
typedef long long ll;

const int maxn=3e5+5;
const int maxm=1000;
ll ans;
int a[maxn],pri[maxm+5],cnt;
bool vis[maxm+5];
map<int,int> mp;

void Init(){
    for(rint i=2;i<=maxm;i++){
        if(!vis[i]) pri[++cnt]=i;
        for(rint j=1;j<=cnt&&pri[j]*i<=maxm;j++){
            vis[i*pri[j]]=1;
            if(i%pri[j]==0) break;
        }
    }
}

int main(){
    int n;scanf("%d",&n);
    Init();
    for(rint i=1;i<=n;i++){
        scanf("%d",&a[i]);
        int base=1;
        for(rint j=1;j<=cnt;j++){
            int num=0;
            while(a[i]%pri[j]==0) a[i]/=pri[j],num++;
            if(num%2) base*=pri[j];
        }
        int sq=sqrt(a[i]);
        if(a[i]!=1&&a[i]!=sq*sq) base*=a[i];
        ans+=mp[base];
        mp[base]++;
    }
    printf("%lld\n",ans);
    return 0;
}

T3 多维网络

部分分很足,大概没手都能拿到80pts
正解是容斥,用所有方案减掉不合法方案即可
所有方案用组合数随便写,但要注意这个题稍微卡内存
而我们只需要用到阶乘和阶乘的逆元,只处理这两个东西就行
(不开long long不建图的话好像也可以开的下,但比较危险)
剩下的问题就是怎么不重不漏的求出不合法的情况
我们去枚举某条不合法路径经过的第一个坏点
那么对于某个坏点来说,能与它构成偏序关系的才有可能被在它前面被走到
比如二维情况下,要想第一次走到(3,3),中间有可能会先走到(1,1),这个东西需要容斥出来
我们设这个点为u,它前面某个具有偏序关系的点为v,原点是o,终点是x,dis(a,b)表示从a点到b点的所有方案数
那么第一次经过u点的方案就是\((dis(o,u)-\sum\limits_{v}dis(o,v))*dis(u,x)\)
显然这个具有拓扑关系,可以建出图来跑个拓扑维护这个限制关系
但其实并不用,直接将每个点的坐标累加,把坏点sort按照这个总和sort一下,
没有偏序关系的话,坐标相减一定会出现负数,直接在求组合数的时候判成0就可以解决
这样做常数更小,时间空间开销都不大,能快十倍左右
时间复杂度\(O(n^2d)\)



#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define rint register int
using namespace std;
typedef long long ll;

const int maxn=1e7+5;
const int mod=1e9+7;
int d,n,a[105],ans,f[505];
int suma,jc[maxn],ny[maxn],jcny[maxn];

struct Node{
    int x[105];
    int sumx;
    friend bool operator < (Node aa,Node bb){
        return aa.sumx<bb.sumx;
    }
}b[505];

int qpow(rint y,rint x){
    int base=y,ans=1;
    while(x){
        if(x&1) ans=1LL*ans*base%mod;
        base=1LL*base*base%mod;
        x>>=1;
    }
    return ans;
}

void Init(){
    jc[0]=jc[1]=jcny[0]=jcny[1]=1;
    for(rint i=2;i<=suma;i++) jc[i]=1LL*jc[i-1]*i%mod;
    jcny[suma]=qpow(jc[suma],mod-2);
    for(rint i=suma-1;i>=2;i--) jcny[i]=1LL*jcny[i+1]*(i+1)%mod;
}

int C(rint x,rint y){
    if(x<0||y<0) return 0;
    return 1LL*jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}

int Calc(rint x){
    int base=1,sum=b[x].sumx,num;
    for(rint i=1;i<=d;i++){
        base=1LL*base*C(sum,b[x].x[i])%mod;
        sum-=b[x].x[i];
    }
    for(rint i=1;i<x;i++){
        num=1,sum=b[x].sumx-b[i].sumx;
        for(rint j=1;j<=d;j++){
            num=1LL*num*C(sum,b[x].x[j]-b[i].x[j])%mod;
            sum-=b[x].x[j]-b[i].x[j];
        }
        base=(base-1LL*num*f[i]%mod+mod)%mod;
    }
    f[x]=base;
    num=1,sum=suma-b[x].sumx;
    for(rint i=1;i<=d;i++){
        num=1LL*num*C(sum,a[i]-b[x].x[i])%mod;
        sum-=a[i]-b[x].x[i];
    }
    base=1LL*base*num%mod;
    return base;
}

int main(){
    scanf("%d%d",&d,&n);
    for(rint i=1;i<=d;i++)
        scanf("%d",&a[i]),suma+=a[i];
    Init();
    for(rint i=1;i<=n;i++)
        for(rint j=1;j<=d;j++){
            scanf("%d",&b[i].x[j]);
            b[i].sumx+=b[i].x[j];
        }
    sort(b+1,b+n+1);
    ans=1;
    int sum=suma;
    for(rint i=1;i<=d;i++){
        ans=1LL*ans*C(sum,a[i])%mod;
        sum-=a[i];
    }
    for(rint i=1;i<=n;i++) ans=(ans-Calc(i)+mod)%mod;
    printf("%d\n",ans);
    return 0;
}

上一篇:AC自动机的一些小问题


下一篇:Java数学函数的使用