题解:
比较明显的期望DP
设f[n]表示 n 变成1的期望步数
则f[n]=1+\frac{\sum_{d|n}f[d]}{d(n)} (d(n)表示n的因子个数)
移一下项\frac{(d(n)-1)f[n]}{d(n)}=1+\frac{\sum_{d|n,d<n}f[d]}{d(n)}
f[n]=\frac{d(n)+\sum_{d|n,d<n}f[d]}{d(n)-1}
我们发现这个转移其实只与n的所有质因子的次幂的可重集有关
根据一个结论,我们知道了在n<=10^24是,本质不同的可重集个数为170000+
我们可以爆搜出所有的可重集,然后进行DP即可
代码:(第一次写双longlong压12位,感觉挺不错的)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
#define N 30
#define LL unsigned long long
char s[N+5];
const LL pw=1000000000000ull;
struct big{
LL a[2];
big(){a[0]=a[1]=0;}
big(int x){a[0]=x;a[1]=0;}
big operator *= (const int x){
a[0]*=x;a[1]*=x;
if(a[0]>=pw)a[1]+=a[0]/pw,a[0]%=pw;
return *this;
}
bool operator < (const big &t)const{
return a[1]<t.a[1]||(a[1]==t.a[1]&&a[0]<t.a[0]);
}
int operator %(const int t)const{
return (pw*(a[1]%t)+a[0]%t)%t;
}
big operator /= (const int x){
a[0]+=pw*(a[1]%x);
a[1]/=x;a[0]/=x;
return *this;
}
void read(){
if(!~scanf("%s",s))return;
int i,j,len=strlen(s);a[0]=a[1]=0;
for(i=0;i*12<len;i++)
for(j=min(12,len-i*12);j>=1;j--)
a[i]=a[i]*10-48+s[len-i*12-j];
}
}n,lim;
LL gethh(vector<int> a)
{
sort(a.begin(),a.end());
LL hh=0;
for(int i=0;i<int(a.size());i++)
hh=(137ull*hh+a[i]);
return hh;
}
int prime[N]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71};
vector<int> now,zer;
map<LL,int> id;
pair<big,vector<int> > tmp[200005];
int tcnt,ans[200005],sum[200005][21];
void dfs(big x,int k,int pre,vector<int> e)
{
//printf("%012llu%012llu\n",x.a[1],x.a[0]);
tmp[++tcnt]=make_pair(x,e);
e.push_back(0);
for(int i=1;i<=pre&&(x*=prime[k])<lim;i++)
e.back()++,dfs(x,k+1,i,e);
}
const int mod=1000000007;
int ksm(int x,int y)
{
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
y>>=1;x=1ll*x*x%mod;
}
return ret;
}
int main()
{
freopen("div.in","r",stdin);
freopen("div.out","w",stdout);
int m,i,j,x;
lim.a[1]=pw;
lim.a[0]=1;
dfs(big(1),1,80,zer);
for(i=1;i<=tcnt;i++)id[gethh(tmp[i].second)]=i;
for(i=2;i<=tcnt;i++){
for(j=tmp[i].second.size()-1;j>=0;j--){
sum[i][j]=sum[i][j+1];
if(tmp[i].second[j]){
vector<int> tt=tmp[i].second;
tt[j]--;
sum[i][j]=(sum[i][j]+sum[id[gethh(tt)]][j])%mod;
}
}
int d=1;
for(j=tmp[i].second.size()-1;j>=0;j--)
d*=tmp[i].second[j]+1;
ans[i]=1ll*(d+sum[i][0])*ksm(d-1,mod-2)%mod;
for(j=0;j<=19;j++)
sum[i][j]=(sum[i][j]+ans[i])%mod;
}
//printf("%d\n",tcnt);
while(n.read(),~scanf("%d",&m)){
vector<int>a;
for(i=1;i<=m;i++){
scanf("%d",&x);
int cnt=0;
while(n%x==0)n/=x,cnt++;
a.push_back(cnt);
}
printf("%d\n",ans[id[gethh(a)]]);
}
}
题解:恶心分类讨论题
其实各种分类讨论都是可以过的
简单讲讲我自己的分类讨论
首先把含有###或##.后面的字符串断开
然后按照##*来进行分段
考虑每一个段,如果当前的干扰器个数大于等于2,就一定可以把这一段的干扰器全部拿完
否则就考虑一下几种情况:(假设此时的干扰器个数为0)
1、*..#...*..#
2、#*....#..*
3、#*...*..#
4、*..#....#*
5、*..#....#..*
6、*..#....#*.....#...#*
我们设 *#. 的情况为flg1, .#* 的情况为flg2
那么出现11、22、21的情况则可以拿到2个干扰器,进而拿完所有的干扰器
而出现12的情况(4)则只能拿一个,而出现情况(5)则无法通过此段
情况(6)就是情况(4)的嵌套
注意一下判断就行了
代码:(考试的时候少判了“if(flg1){now=2;return 1;}”以及开小了数组,100->20。。。难受)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 3000005
char a[N];
int cnt,pos[N],sum[N],ans;
bool solve(int l,int r,int &now)
{
if(now>=2)return 1;
bool flg1=0,flg2=0;
for(int i=l;i<=r;i++){
if(a[i]=='*'){
now++,ans=max(ans,now);
if(flg1){now=2;return 1;}
}
if(now>=2)return 1;
if(a[i]=='#')continue;
if(i<r-1&&a[i+1]=='#'&&a[i]=='*'&&a[i+2]=='*'){now=2;return 1;}
if(i<r-1&&a[i+1]=='#'&&a[i]=='*'&&a[i+2]=='.'){
if(flg1||flg2){now=2;return 1;}
if(!flg1)flg1=1,now--;
}
if(i<r-1&&a[i+1]=='#'&&a[i]=='.'&&a[i+2]=='*'){
if(!flg2)flg2=1;
else{now=2;return 1;}
flg1=0;
}
if(i<r-1&&a[i+1]=='#'&&a[i]=='.'&&a[i+2]=='.'){
if(!now)return 0;
else now--,flg1=1,flg2=0;
}
}
return 1;
}
int main()
{
freopen("tower.in","r",stdin);
freopen("tower.out","w",stdout);
int T,n,i;
scanf("%d",&T);
while(T--){
scanf("%s",a+1);
n=strlen(a+1);cnt=0;
a[n+1]=a[n+2]='#';
for(i=1;i<=n;i++){
sum[i]=sum[i-1];
if(a[i]=='*')sum[i]++;
if(a[i]=='#'&&a[i+1]=='#')pos[++cnt]=i+1;
if(a[i]=='#'&&a[i+1]=='#'&&(a[i+2]=='#'||a[i+2]=='.')){n=i-1;break;}
}
pos[++cnt]=n+2;
int now=0;ans=0;
for(i=1;i<=cnt;i++){
bool flg=solve(pos[i-1]+1,pos[i]-2,now);
if(now>=2)now=sum[pos[i]-2]-(i-1),ans=max(ans,now);
if(!flg)break;
if(i<cnt){
if(now)now--;
else break;
}
}
printf("%d\n",ans);
}
//1 ...*.#.*..#..*
}
/*
1
..*..#....*.#..*..#
*/
题解:利用取值分界点的单调性+单调队列来优化DP
首先发现空位只填-1或K是最优的
再发现真实答案一定是可以取完整个区间[1,n]的(如果a1或an为负数则向内缩进一步)
于是我们设f[j][i]表示在[1,i]中放了j个-1的小b的答案的最小值
那么我们真实答案就是sum[n]-j*(K+1)-f[j][n](sum[n]表示1~n所有空位都填K的前缀和)
则有f[j][i]=min_{1~i}(max(f[j-1][k-1],sum[i]-sum[k]))
注意整个DP是主动放置-1来分段,而且我们的决策点只能是在偶数位上,如果奇数位上有负数,这个DP就会被强制分段
这就是O(n^3)的做法
考虑优化
我们要求的是两个值的max的最小值
我们猜测存在一个分界点o
使得k∈[1,o]时,max(f[j-1][k-1],sum[i]-sum[k])=sum[i]-sum[k]
k∈[o,i)时,max(f[j-1][k-1],sum[i]-sum[k])=f[j-1][k-1]
其实这也是显然的,因为sum[i]-sum[k]是关于k单调递减的,f[j-1][k-1]是关于k单调递增的(因为如果放置的-1数目不变,越到后面,小b的最大子段和就会越大)
我们再大胆猜测o是关于i单调递增的
因为若k∈[1,o],则要满足条件sum[i]-sum[k]>=f[j-1][k-1]
移一下项sum[i]>=f[j-1][k-1]+sum[k]
由于sum[i]是递增的,sum[k]是递增的,f[j-1][k-1]也是递增的
所以当i=i+1之后,会有更多的k属于[1,o]这个区间
现在我们利用单调性维护出了分段点o
那么前半段的贡献可以通过维护sum[k]的前缀最大值来完成(现在的目的是求sum[i]-sum[k]的最小值)
后半段的贡献可以通过单调队列来维护出f[j-1][k-1]的最小值
代码:(理论上来说可以直接维护分界点o来转移的,但是过不了对拍。。。)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
#define LL long long
const int INF=0x3f3f3f3f;
int a[N],q[N],he,ta;
int f[5005][N],sum[N];
int main()
{
freopen("subsegment.in","r",stdin);
freopen("subsegment.out","w",stdout);
int n,K,i,j,k;
scanf("%d%d",&n,&K);
for(i=1;i<=n;i++)
scanf("%d",&a[2*i-1]);
n=2*n-1;
a[1]=max(a[1],0);a[n]=max(a[n],0);
for(i=1;i<=n;i++)sum[i]=sum[i-1]+(i&1?a[i]:K);
memset(f,0x3f,sizeof(f));
for(i=1,k=0;i<=n;i++){
if(a[i]<0)k=i;
f[0][i]=max(k?f[0][k-1]:0,sum[i]-sum[k]);
}
int ans=sum[n]-f[0][n],o,mx;
for(j=1;j<=(n>>1);j++){
ta=o=0,he=1,mx=-INF;
for(i=1,k=0;i<=n;i++){
if(a[i]<0)k=i,o=i-1,ta=0,he=1,mx=-INF;
if(k<=i)f[j][i]=max(k?f[j][k-1]:0,sum[i]-sum[k]);
if(k==i)continue;
while(o+2<=i&&f[j-1][o+2-1]+sum[o+2]<=sum[i])
o+=2,mx=max(mx,sum[o]);
while(he<=ta&&o>=q[he])he++;
f[j][i]=min(f[j][i],sum[i]-mx);
if(he<=ta)f[j][i]=min(f[j][i],f[j-1][q[he]-1]);
if(!(i&1)){
while(he<=ta&&f[j-1][i-1]<=f[j-1][q[ta]-1])
ta--;
q[++ta]=i;
}
}
ans=max(ans,sum[n]-j*(K+1)-f[j][n]);
}
printf("%d\n",ans);
}