省选模拟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)\)了.
上下界什么的挺重要的.
这个改变枚举对象也很重要的.
来源:https://www.cnblogs.com/hzoi2018-xuefeng/p/12364150.html