1. 程式人生 > 其它 >noip模擬測試38

noip模擬測試38

這兩天考試覺得自己不在狀態,做題也沒有什麼思路,休整一下,儘快恢復狀態。

T1 a

這道題,考試的時候我一直認為要用線段樹,可是自己不會用,還不斷的hack掉自己的線段樹思路,最後沒辦法只能打了一個\(m^2*n^2\)的暴力,加上一些特殊性質,本來能拿50分,結果自己沒有考慮周到,在測試點分治的時候沒弄好,把那個二級資料直接略過了,導致只拿了30分。而正解的思路就是在暴力的基礎上優化掉一個m,達到 \(n^2*m\),具體做法就是我們只列舉左上角的頂點,因為滿足要求的右下角頂點必定在一個單調遞增的區間中,所以滿足決策單調性,可以使用二分在log的時間內算出合法的區間數,但是需要加上一些剪枝優化,具體實現見程式碼:

AC_code
#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=5e4+10;
const int INF=1e7+10;
int n,m;
long long ans;
char s[35][N];
int sum[35][N];
ii read()
{
	int x=0;
	bool f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			f=0;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
signed main()
{
	int l,r;
	n=read();
	m=read();
	for(re i=1;i<=n;i++)
		scanf("%s",s[i]+1);
	l=read();
	r=read();
	for(re i=1;i<=n;i++)
	{
		for(re j=1;j<=m;j++)
		{
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
			if(s[i][j]=='1')
				sum[i][j]+=1;
		}
	}
	int L,R,nl,nr;
	for(re i=1;i<=n;i++)
	{
		for(re k=i;k<=n;k++)
		{
		  for(re j=1;j<=m;j++)
		  {
				if(sum[k][m]-sum[i-1][m]-sum[k][j-1]+sum[i-1][j-1]<l || sum[k][j]-sum[i-1][j]-sum[k][j-1]+sum[i-1][j-1] >r) continue;
				L=j,R=m,nl=m+1,nr=m+1;
				if(sum[k][j]-sum[i-1][j]-sum[k][j-1]+sum[i-1][j-1]>=l) nl=j; 
				else 
					while(L<=R)
					{
						int mid=(L+R)>>1;
						if(sum[k][mid]-sum[i-1][mid]-sum[k][j-1]+sum[i-1][j-1]>=l)
						{
							nl=mid;
							R=mid-1;
						}
						else
							L=mid+1;
					}
				L=j,R=m;
				if(sum[k][m]-sum[i-1][m]-sum[k][j-1]+sum[i-1][j-1]<=r) nr=m;
				else
					while(L<=R)
					{
						int mid=(L+R)>>1;
						if(sum[k][mid]-sum[i-1][mid]-sum[k][j-1]+sum[i-1][j-1]<=r)
						{
							nr=mid;
							L=mid+1;
						}
						else
							R=mid-1;
					}
				ans+=nr-nl+1;
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

T2 b

思路:這裡記 a 的最大值為 x。
將問題轉化為“對 \(i∈[1,1e5]\),求多少種選擇方案使得 \(gcd=i\)
繼續轉化為“對 \(i∈[1,1e5]\),求多少種選擇方案使得 \(i|gcd\),
等價於對\(i∈[1,1e5]\),求多少種選擇方案使得選的所有數均為 i 的倍數
我們預處理出\(cnt_{i,j}\)表示第 i 行中有多少個數是 j 的倍數,
那麼答案就是\(∏\limits_{i=1}^n = (cnt_{i,j} + 1) − 1\)
最後注意去重,所以我們要倒序列舉,每次去掉出現過的當前數的倍數的答案,具體實現見程式碼:

AC_code
#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int mo=1e9+7;
const int N=1e5+10;
int n,m,maxx,ans;
int a[30][N];
int to[30][N],cnt[30][N];
int an[N];
ii read()
{
	int x=0;
	bool f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			f=0;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
signed main()
{
	n=read();
	m=read();
	for(re i=1;i<=n;i++)
	{
		for(re j=1;j<=m;j++)
		{
			a[i][j]=read();
			maxx=max(maxx,a[i][j]);
			to[i][a[i][j]]++;
		}
	}
	for(re i=1;i<=n;i++)
	{
		for(re j=1;j<=maxx;j++)
			for(re k=1;k*j<=maxx;k++)
				cnt[i][j]+=to[i][j*k];
	}
	for(re i=maxx;i;i--)
	{
		an[i]=1;
		for(re j=1;j<=n;j++)
			an[i]=(an[i]%mo*(cnt[j][i]+1))%mo;
		an[i]--;
		for(re j=2;j*i<=maxx;j++)
			an[i]-=an[j*i];
		ans=(ans%mo+an[i]%mo*i%mo+mo)%mo;
	}		
	printf("%lld\n",(ans%mo+mo)%mo);
	return 0;
}