【学习笔记】Public NOIP Round #3 简要题解

A.移除石子

模拟题。从左上角开始处理,讨论几种情况即可。

复杂度 O ( T n log ⁡ n ) O(Tn\log n) O(Tnlogn)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
int T,n,lsh[3005],X[3005],Y[3005],cnt;
deque<int>v[3005];
int get(int x){return lower_bound(lsh+1,lsh+1+cnt,x)-lsh;}
void print(pair<int,int>x,pair<int,int>y){
	pair<int,int>le=make_pair(max(x.fi,y.fi),max(x.se,y.se));int d=max(abs(x.fi-y.fi),abs(x.se-y.se));
	cout<<le.fi-d<<' '<<le.se-d<<' '<<le.fi<<' '<<le.se<<"\n";
}
int main(){
	freopen("stone.in","r",stdin);
	freopen("stone.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n,cnt=0;for(int i=1;i<=n;i++)cin>>X[i]>>Y[i],lsh[++cnt]=X[i];
		sort(lsh+1,lsh+1+cnt),cnt=unique(lsh+1,lsh+1+cnt)-lsh-1;
		for(int i=1;i<=cnt;i++)v[i].clear();for(int i=1;i<=n;i++)v[get(X[i])].push_back(Y[i]);
		cout<<"Yes"<<"\n";
		pair<int,int>fuck=make_pair(0x3f3f3f3f,0x3f3f3f3f);
		for(int i=1;i<=cnt;i++){
			sort(v[i].begin(),v[i].end());
			while(v[i].size()>1){
				int x=v[i][0];v[i].pop_front();
				int y=v[i][0];v[i].pop_front();
				if(y<fuck.se){
					int d=y-x;
					cout<<lsh[i]-d<<' '<<x<<' '<<lsh[i]<<' '<<y<<"\n";
				}
				else if(y>fuck.se){
					print(fuck,make_pair(lsh[i],x)),fuck=make_pair(0x3f3f3f3f,0x3f3f3f3f),v[i].push_front(y);					
				}
				else {
					if(lsh[i]-fuck.fi>y-x)print(make_pair(lsh[i],x),make_pair(lsh[i],y));
					else if(lsh[i]-fuck.fi<y-x)print(fuck,make_pair(lsh[i],y)),v[i].push_front(x);
					else {
						cout<<fuck.fi<<".5"<<' '<<x<<' '<<lsh[i]<<".5"<<' '<<y<<"\n";
					}
				}
			}
			if(v[i].size()){
				if(fuck.fi==0x3f3f3f3f)fuck=make_pair(lsh[i],v[i][0]);
				else {
					print(fuck,make_pair(lsh[i],v[i][0])),fuck=make_pair(0x3f3f3f3f,0x3f3f3f3f);
				}
			}
		}
	}
}

B.抓内鬼

这题没想到抽屉原理纯属脑抽

首先把最短路图建出来,如果 1 → n 1\to n 1n直接相连的话直接把 { 1 , n } \{1,n\} {1,n}放到同一组即可。

否则我们假设不存在长度为 3 3 3的最短路。设直接与 1 1 1相连的点的集合为 S S S,与 n n n相连的点的集合为 T T T,显然 S ∩ T = ∅ S\cap T=\empty ST=,大概是这个样子:

请添加图片描述
显然只需把 S S S或者 T T T全部分到同一组即可。而根据抽屉原理这是显然成立的。

对于长度为 3 3 3的最短路,由于 1 , n 1,n 1,n分在了不同组,所以中间这个点总会与 1 1 1, n n n中的一个分在同一组。这样从 S S S T T T的任何路径都存在一条边满足两个端点被分在了同一组。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
int n,m,K,vis[200005],vis2[200005],a[200005],dis[200005],res[200005];
vector<int>g[200005],vec;
priority_queue<pair<int,int>>q;
struct node{
	int u,v;
}e[200005];
int main(){
	freopen("catch.in","r",stdin);
	freopen("catch.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>K,memset(res,-1,sizeof res);
	int l=K,r=n-K;
	if(K==0){for(int i=1;i<=n;i++)cout<<"P";return 0;}
	if(K==n){for(int i=1;i<=n;i++)cout<<"U";return 0;}
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=m;i++){
		int u,v;cin>>u>>v,g[u].pb(v),g[v].pb(u),e[i]={u,v};
	}
	memset(dis,0x3f,sizeof dis),dis[1]=0,q.push(make_pair(0,1));
	while(q.size()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;vis[u]=1;
		for(auto v:g[u]){
			if(dis[u]+a[v]<dis[v])dis[v]=dis[u]+a[v],q.push(make_pair(-dis[v],v));
		}
	}for(auto u:g[n]){
		if(dis[u]+a[n]==dis[n]){
			vis2[u]=1,vec.pb(u);
		}
	}vec.pb(n);
	if(vis2[1]){
		if(l>=2)res[1]=res[n]=0,l-=2;
		else if(r>=2)res[1]=res[n]=1,r-=2;
		else {
			cout<<"impossible";
			return 0;
		}
	}
	else {
		if(l>=vec.size()){
			for(auto x:vec)res[x]=0,l--;
			res[1]=1,r--;
		}
		else if(r>=vec.size()){
			for(auto x:vec)res[x]=1,r--;
			res[1]=0,l--;
		}
		else if(l>=n-vec.size()){
			for(int i=1;i<n;i++){
				if(!vis2[i])res[i]=0,l--;
			}res[n]=1,r--;
		}
		else {
			for(int i=1;i<n;i++){
				if(!vis2[i])res[i]=1,r--;
			}res[n]=0,l--;
		}
	}
	for(int i=1;i<=n;i++){
		if(res[i]==-1){
			if(l)res[i]=0,l--;else res[i]=1,r--;
		}cout<<(res[i]?"P":"U");
	}
}

C.异或序列

水题,但是我没看出来

考虑最朴素的状态定义 ,设 d p i dp_i dpi表示以 i i i结尾的合法序列数目。那么 d p i = ∑ j < i d p j − ∑ j < i , p ( i ) = p ( j ) d p i ⊕ j dp_{i}=\sum_{j<i}dp_j-\sum_{j<i,p(i)=p(j)}dp_{i\oplus j} dpi=j<idpjj<i,p(i)=p(j)dpij 。其中 p ( i ) p(i) p(i)表示二进制下最高位的 1 1 1

不难看出后面的式子可以前缀和优化,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
int n,mod;
ll res,dp[1000005],sum[1000005];
int main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>mod,dp[1]=sum[1]=res=1;
	for(int i=2;i<=n;i++){
		dp[i]=sum[i-1]+1;
		int j=29;while(!(i>>j&1))j--;
		for(int k=j-1;k>=0;k--){
			if(i>>k&1){
				dp[i]=(dp[i]-sum[(1<<(k+1))-1]+sum[(1<<k)-1])%mod;
			}
		}sum[i]=(sum[i-1]+dp[i])%mod;
	}cout<<(sum[n]+mod)%mod;
}

D.数圈圈

分治好题。之前没遇到过这个想法。

考虑对矩阵分治,每次选择长的一边割开,然后计算跨过 mid \text{mid} mid的圈的数目。

在这里插入图片描述
枚举 u u u, v v v,只需将左右方案数相乘。

l u l_u lu表示向左延伸的最大长度, d i , u d_{i,u} di,u表示第 i i i列向下延伸的最大长度。

要求 ∑ i = max ⁡ ( l u , l v ) m i d [ d i , u ≥ v ] \sum_{i=\max(l_u,l_v)}^{mid}[d_{i,u}\ge v] i=max(lu,lv)mid[di,uv]。不妨假设 l u > l v l_u>l_v lu>lv(后者再做一遍即可),原式变成了 ∑ i = l u m i d [ d i , u ≥ v ] \sum_{i=l_u}^{mid}[d_{i,u}\ge v] i=lumid[di,uv],只有 v v v一个变量,用桶维护即可。

单层复杂度 O ( x y + x 2 ) = O ( x y ) O(xy+x^2)=O(xy) O(xy+x2)=O(xy) x ≤ y x\le y xy),即矩形面积。每一层复杂度 O ( n m ) O(nm) O(nm),总复杂度 O ( n m ( log ⁡ n + log ⁡ m ) ) O(nm(\log n+\log m)) O(nm(logn+logm))

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
int n,m;
int up[2005][2005],down[2005][2005],le[2005][2005],ri[2005][2005],siz[2005][2005],siz2[2005][2005],tong[2005];
char s[2005][2005];
ll solve(int X,int X2,int Y,int Y2){
	if(X==X2||Y==Y2)return 0;
	ll res=0;
	if(X2-X>Y2-Y){
		int mid=X+X2>>1;
		res+=solve(X,mid,Y,Y2)+solve(mid+1,X2,Y,Y2);
		for(int i=Y;i<=Y2;i++)for(int j=Y;j<=Y2;j++)siz[i][j]=siz2[i][j]=0;
		for(int i=Y;i<=Y2;i++){
			int l=max(X,up[mid][i]),tot=0;
			for(int j=i+1;j<=Y2;j++)tong[j]=0;
			for(int j=l;j<=mid;j++)tong[min(Y2,ri[j][i])]++;
			for(int j=Y2;j>i;j--){
				tot+=tong[j];if(max(X,up[mid][j])<=l)siz[i][j]+=tot;
			}
		}
		for(int i=Y2;i>=Y;i--){
			int l=max(X,up[mid][i]),tot=0;
			for(int j=i-1;j>=Y;j--)tong[j]=0;
			for(int j=l;j<=mid;j++)tong[max(Y,le[j][i])]++;
			for(int j=Y;j<i;j++){
				tot+=tong[j];if(max(X,up[mid][j])<l)siz[j][i]+=tot;
			}
		}
		for(int i=Y;i<=Y2;i++){
			int l=min(X2,down[mid+1][i]),tot=0;
			for(int j=i+1;j<=Y2;j++)tong[j]=0;
			for(int j=mid+1;j<=l;j++)tong[min(Y2,ri[j][i])]++;
			for(int j=Y2;j>i;j--){
				tot+=tong[j];if(min(X2,down[mid+1][j])>=l)siz2[i][j]+=tot;
			}
		}
		for(int i=Y2;i>=Y;i--){
			int l=min(X2,down[mid+1][i]),tot=0;
			for(int j=i-1;j>=Y;j--)tong[j]=0;
			for(int j=mid+1;j<=l;j++)tong[max(Y,le[j][i])]++;
			for(int j=Y;j<i;j++){
				tot+=tong[j];if(min(X2,down[mid+1][j])>l)siz2[j][i]+=tot; 
			}
		}
		for(int i=Y;i<=Y2;i++)for(int j=i+1;j<=Y2;j++)if(s[mid][i]==s[mid+1][i]&&s[mid][j]==s[mid+1][j])res+=(ll)siz[i][j]*siz2[i][j];
	}
	else {
		int mid=Y+Y2>>1;
		res+=solve(X,X2,Y,mid)+solve(X,X2,mid+1,Y2);
		for(int i=X;i<=X2;i++)for(int j=X;j<=X2;j++)siz[i][j]=siz2[i][j]=0;
		for(int i=X;i<=X2;i++){
			int l=max(Y,le[i][mid]),tot=0;
			for(int j=i+1;j<=X2;j++)tong[j]=0;
			for(int j=l;j<=mid;j++)tong[min(X2,down[i][j])]++;
			for(int j=X2;j>i;j--){
				tot+=tong[j];if(max(Y,le[j][mid])<=l)siz[i][j]+=tot;
			}
		}
		for(int i=X2;i>=X;i--){
			int l=max(Y,le[i][mid]),tot=0;
			for(int j=i-1;j>=X;j--)tong[j]=0;
			for(int j=l;j<=mid;j++)tong[max(X,up[i][j])]++;
			for(int j=X;j<i;j++){
				tot+=tong[j];if(max(Y,le[j][mid])<l)siz[j][i]+=tot;
			}
		}
		for(int i=X;i<=X2;i++){
			int l=min(Y2,ri[i][mid+1]),tot=0;
			for(int j=i+1;j<=X2;j++)tong[j]=0;
			for(int j=mid+1;j<=l;j++)tong[min(X2,down[i][j])]++;
			for(int j=X2;j>i;j--){
				tot+=tong[j];if(min(Y2,ri[j][mid+1])>=l)siz2[i][j]+=tot;
			}
		}
		for(int i=X2;i>=X;i--){
			int l=min(Y2,ri[i][mid+1]),tot=0;
			for(int j=i-1;j>=X;j--)tong[j]=0;
			for(int j=mid+1;j<=l;j++)tong[max(X,up[i][j])]++;
			for(int j=X;j<i;j++){
				tot+=tong[j];if(min(Y2,ri[j][mid+1])>l)siz2[j][i]+=tot;
			}
		}
		for(int i=X;i<=X2;i++)for(int j=i+1;j<=X2;j++)if(s[i][mid]==s[i][mid+1]&&s[j][mid]==s[j][mid+1])res+=(ll)siz[i][j]*siz2[i][j];
	}
	return res;
}
int main(){
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	cin>>n>>m;for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			up[i][j]=(s[i-1][j]==s[i][j])?up[i-1][j]:i;
			le[i][j]=(s[i][j-1]==s[i][j])?le[i][j-1]:j;
		}
	}
	for(int i=n;i>=1;i--){
		for(int j=m;j>=1;j--){
			down[i][j]=(s[i+1][j]==s[i][j])?down[i+1][j]:i;
			ri[i][j]=(s[i][j+1]==s[i][j])?ri[i][j+1]:j;
		}
	}
	cout<<solve(1,n,1,m);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值