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

noip模擬測試10

T1

這道題在考場上想到了二維字首和,就是自己算字首和的方式有點麻煩,導致花的時間較長,但還是成功搞了出來。

因為暴力計算的話需要不停列舉左上角和右下角的 i ,j, 時間複雜度為 n^4 ,我當時就想一種能不能減少一層或者兩層列舉內容,但是沒什麼思路。

考慮這樣一個性質:在模k意義下相同的字首和,任意兩個相減可以被 k 整除

這道題正解為 n^3 ,的複雜度,我們考慮將每一列壓縮,這樣,我們只需要通過列舉每個矩形區域的上下邊界,就可以求得答案

注意:當餘數為0的時候要多算一次答案

程式碼:

#include<bits/stdc++.h>
#define re register int
#define int long long
#define ll long long
using namespace std;
const int N=410,M=1e6+10;
int n,m,k,ans;
int a[N][N],sum[N][N],s[N];
int vis[M];
ll read()
{
	ll x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9')
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}	
	return x;
}
#undef int
int main()
{
	#define int long long
	n=read();
	m=read();
	k=read();
	for(re i=1;i<=n;i++)
	{
		for(re j=1;j<=m;j++)
		{
			a[i][j]=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]+a[i][j];
		}	
	}
	for(re i=0;i<n;i++)
	{
		for(re j=i+1;j<=n;j++)
		{
			for(re p=1;p<=m;p++)
			{
				s[p]=(sum[j][p]-sum[i][p]+k)%k;
				ans+=vis[s[p]]++;
			}
			ans+=vis[0];
			for(re p=1;p<=m;p++)
				vis[s[p]]=0;
			vis[0]=0;
			
		}
	}
	printf("%lld\n",ans);
	return 0;
}

T2

 這道題,是一道貪心題,但是我在考場上沒看出來,當時之打了個dfs。

 其實不難看出,如果要修建小隊,則在距離他的最遠的祖先哪裡最優

 所以我們可以按深度從大到小排序,從深度最大的兒子向上更新答案

 程式碼:

#include<bits/stdc++.h>
#define re register int
#define next neet
#define INF 9999999
using namespace std;
const int N=1e5+10;
int n,k,t,ans,tot;
int fa[N],deep[N],num[N],o[N];
int to[N<<1],next[N<<1],head[N<<1];
struct node
{
	int out,ff;
};
int my(int a,int b)
{
	return deep[a]>deep[b];
}
node gett(int v)
{
	node P;
	int u=v,out=INF,mf=v;
	for(re i=1;i<=k;i++)
	{
		u=fa[u];
		out=min(out,o[u]+i);
		mf=u;
		if(u==0)
			break;
		
	}
	P.out=out;
	P.ff=mf;
	return P;
}
void change(int x)
{
	int u=x;
	for(re i=1;i<=k;i++)
	{
		u=fa[u];
		o[u]=min(o[u],i);
		if(u==0)
			break;
	}
}
void add(int x,int y)
{
	to[++tot]=y;
	next[tot]=head[x];
	head[x]=tot;
}
void dfs(int st,int f)
{
	deep[st]=deep[f]+1;
	for(re i=head[st];i;i=next[i])
	{
		int p=to[i];
		if(p==f)
			continue;
		fa[p]=st;
		dfs(p,st);
	}
}
int main()
{
	scanf("%d%d%d",&n,&k,&t);
	int a,b,c,d;
	for(re i=2;i<=n;i++)
	{
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	deep[0]=-1;
	dfs(1,0);
	for(re i=1;i<=n;i++)
	{
		num[i]=i;
		o[i]=INF;
	}
	o[0]=INF;
	sort(num+1,num+n+1,my);
	for(re i=1;i<=n;i++)
	{
		int v=num[i];
		node X;
		X=gett(v);
		o[v]=min(o[v],X.out);
		if(o[v]>k)
		{
			++ans;
			o[X.ff]=0;
			change(X.ff);
		}
	}
	printf("%d\n",ans);
	return 0;
}

T3

這道題,確實比較有難度

看到對於一個區間的操作,我當時想了線段樹,書裝陣列,但是沒什麼思路。

正解是利用差分!!,這道題思路非常妙,考慮一個差分陣列,那麼考慮對原 010101 串進行差分。 例如樣例,原串是 0100010,這裡設第 000 位和第 n+1n+1n+1 位都為 000,用紅色數字表示。
則差分之後是 0110011,那麼我們對原串 [l,r] ,進行翻轉,相當於把 a[l]^=1,a[r+1]^=1,

而我們的目的是將差分陣列全部變為0,考慮轉移時的情況:

1.兩端都是 0,變為 1

2.兩端都是1.變為 0

3.一個 1,一個 0;

可見,只有第二種情況對答案有貢獻,所以我們之考慮第二種情況;

我們將每個 i與i+b[j] 連一條長度為1的邊,那麼將 [L,R] 的區間翻轉的最小次數就是 L到 R 的最短路

我們將所有的 1 進行狀態壓縮,易得:

t=s^(1<<i-1)^(1<<j-1)

f[t]=min(f[t],f[s]+dis[i][j]);

最後輸出答案 f[0],即可

程式碼:

#include<bits/stdc++.h>
#define re register int
using namespace std;
const int N=4e4+10;
int n,k,m,sum;
int b[N],pos[N],dis[N],q[N];
int f[(1<<16)+5];
int d[N>>2][N>>2];
bool vis[N];
void bfs()
{
	int u,v,l,r;
	for(re i=1;i<=sum;i++)
	{
		memset(dis,0x3f,sizeof(dis));
		memset(vis,0,sizeof(vis));
		dis[pos[i]]=0;
		vis[pos[i]]=1;
		l=r=1;
		q[l]=pos[i];
		while(l<=r)
		{
			u=q[l];
			++l;
			for(re j=1;j<=m;j++)
			{
				v=u-b[j];
				if(v>=1&&(!vis[v]))
				{
					dis[v]=dis[u]+1;
					vis[v]=1;
					q[++r]=v;
				}
				v=u+b[j];
				if(v<=n+1&&(!vis[v]))
				{
					dis[v]=dis[u]+1;
					vis[v]=1;
					q[++r]=v;
				}
			}
		}
		for(re j=1;j<=sum;j++)
			d[i][j]=dis[pos[j]];
	}
}
int main()
{
	scanf("%d%d%d",&n,&k,&m);
	int a;
	for(re i=1;i<=k;i++)
	{
		scanf("%d",&a);
		vis[a]^=1;
		vis[a+1]^=1;
	}
	for(re i=1;i<=m;i++)
		scanf("%d",&b[i]);
	for(re i=1;i<=n+1;i++)
		if(vis[i])
			pos[++sum]=i;
	bfs();
	memset(f,0x3f,sizeof(f));
	f[(1<<sum)-1]=0;
	for(re s=(1<<sum)-1;s;s--)
	{
		int p=1;
		int ss=s;
		while((ss&1)==0)
		{
			++p;
			ss>>=1;
		}
		for(re i=p+1;i<=sum;i++)
		{
			int t=s^(1<<(i-1))^(1<<(p-1));
			f[t]=min(f[t],f[s]+d[p][i]);
		}
	}
	printf("%d\n",f[0]);
	return 0;
}