省选模拟赛Round2Day1 随机除法 炮塔 最大子段和

 

题解:

比较明显的期望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
..*..#....*.#..*..#
*/

 

 

 

image 

image 

image

 

 

题解:利用取值分界点的单调性+单调队列来优化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);
}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章