省选模拟(29-)

省选模拟31

1.Skip
树状数组+静态凸包
我们先写出来裸\(O(n^2)\)dp.
发现calc(i,j)中有ij形式的成分.
果断凸包维护.
把(j,val(j))看作点,题目要求截距最大.
维护上凸包.
打完后发现样例一个也过不去.
原来转移有条件:满足\(a\)单调递增.
那么就维护树状数组而后在\(log\)个凸包里查询,修改.
\(O(nlog^2)\)

但是Kai586123的做法是按照a排序而后cdq一下.
按横坐标归并一下左右区间,然后扫到左区间的点建凸包,扫到右区间的点二分斜率似乎不是更新答案.
因为转移只会按照a升序转移,所以这样做是对的.

2.String
dp
很遗憾最后没有能打出来.
先考虑80分状压dp做法的思路.
其实这个思路很朴素,只是我认为很难打.
依次枚举每个位置选的是哪种字符.
然后计算出来在前t位确定的情况下后面有多少种情况.
如果当前就>n就说明这一位不能再往下填了.
考虑怎么计算在已知前t位的情况下计算后面的情况数.
设现在已确定cnt种颜色.显然k>=cnt才行.
然后对于k-cnt的剩下的颜色随便选就行,反正我们并不关注.
然后对这k种颜色全排列.
这也是我意想不到的地方.
我没想到k=8支持这种骚操作.
现在就找到所有这k个位置的颜色还剩多少个能用.
再记录一下上个位置是谁就可以记忆化dp了.
想要ac的话.
能够发现k=8的最大情况已经>1e18了.
所以对于k>8的情况,前面一定是ababababab.....cdcdcdcdcd....efefefefefef一直到k<=8为止.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18+10;
int k,fir='a',p[10];
ll n;
char s[50];
int vis[300];
map<vector<int>,ll> mp[10];
ll dfs(vector<int> x,int lst){
    int val=x[lst]; sort(x.begin(),x.end());
    for(int i=0;i<k;++i) if(val==x[i]){ lst=i; break;}
    auto it=mp[lst].find(x);
    if(it!=mp[lst].end()) return it->second; ll r=0;
    for(int i=0;i<k;++i) if(x[i]&&i!=lst){
        --x[i],r+=dfs(x,i),++x[i];
        if(r>inf) return mp[lst][x]=inf;
    }
    return mp[lst][x]=r;
}
inline ll A(int n,int m,ll r=1){
    for(int i=1;i<=m;++i){
        r=r*(n-i+1);
        if(r>inf) return inf;
    }
    return r;
}
inline ll calc(int t){//已经确定t位
    vector<int> now; now.resize(k); int cnt=0; ll r=0;
    for(int i='a';i<='z';++i) vis[i]=0;
    for(int i=1;i<=t;++i) ++vis[s[i]];
    for(int i=1;i<=k;++i) p[i]=0;
    for(int i='a';i<='z';++i) if(vis[i]) p[++cnt]=i;
    if(cnt>k) return 0;
    sort(p+1,p+k+1);
    do{
        int lst=0; double tmp; ll a,b;
        for(int i=1;i<=k;++i) now[i-1]=i-vis[p[i]];
        for(int i=1;i<=k;++i) if(now[i-1]<0) goto nxt;
        for(int i=1;i<=k;++i) if(p[i]==s[t]){ lst=i-1; break;}
        a=dfs(now,lst); b=A(26-cnt-(fir-'a'),k-cnt);
        tmp=1.0*a*b; r+=a*b;
        if(tmp>inf||r>inf) return inf; nxt:;
    }while(next_permutation(p+1,p+k+1));
    return r;
}
int main(){
    scanf("%d%lld",&k,&n);
    while(k>8){
        for(int i=1;i<=2*k-1;++i)
            if(i&1) putchar(fir);
            else putchar(fir+1);
        fir+=2; k-=2;
    }
    const int len=k*(k+1)/2;
    vector<int> t; t.resize(k);
    for(int i=0;i<k;++i) mp[i][t]=1;
    for(int i=1,j;i<=len;++i){
        for(j=fir;j<='z';++j) if(j!=s[i-1]){
            s[i]=j; ll tmp=calc(i); n-=tmp;
            if(n<=0){ n+=tmp; break; }
        }
        if(j>'z') return puts("-1")&0;
    }
    return printf("%s",s+1)&0;
}

3.Permutation
?
有个\(O(n^2)\)的dp是显然相邻的序列一定会在某个位置开始不同.
那么我们枚举这个位置j,以及这个位置的数x.
下个序列这个位置一定是x+1.
并且是这样的.
........x n-j+2 n-j+3 .... n
........x+1 x+2 x+3 x+4 .....
只有这样才会使得两个序列相邻.
那么后面序列已知.
前面随便选的方案数是\(C(x-1,j-1)\).
用这个组合数*m位置的差的绝对值.

优化.
发现m位置的差的绝对值是一个|x-j+r|的形式.
所以变为枚举\(x-j(a)\),再枚举j(b).
组合数就是\(C(a+b-1,b-1)=C(a+b-1,a)\)
就变成了一列的组合数就可以\(O(1)\)了.
上下界什么的挺重要的.

这个改变枚举对象也很重要的.

猜你喜欢

转载自www.cnblogs.com/hzoi2018-xuefeng/p/12364150.html
今日推荐