省選模擬賽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);
}

 

 

 

 

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