【学习笔记】NOIP信心赛

签到题

不太理解出题人为啥要放 n = 300 n=300 n=300的丑陋测试点

如果我们把坐标系旋转 45 45 45度,那么上下左右就变成了左上,右上,左下,右下。

那么把它交错排起来就能得到 3 n + 4 3n+4 3n+4个点的做法。

请添加图片描述
但是毒瘤的出题人就是要卡 n = 300 n=300 n=300啊,怎么办呢

没办法,只能搞一个比 3 n 3n 3n还小的方案了。

只需把宽度增大(这样耗费的点数更少),最后再连一个环即可。只用了 512 512 512个点。理论上可以更少。

请添加图片描述

#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
using namespace std;
int n;
vector<pair<int,int>>v;
int main(){
	freopen("hello.in","r",stdin);
	freopen("hello.out","w",stdout);
	cin>>n;
	if(n%3==0){
		int x=1e5,y=1e5;
		for(int i=1;i<=n/3+1;i++){
			v.pb({x,y}),v.pb({x+1,y-1}),v.pb({x-1,y}),v.pb({x,y-1}),v.pb({x+1,y-2});
			x--,y--;
		}
		v.pb({x-1,y+1}),v.pb({x+2,y-2});
		v.pb({x-1,y}),v.pb({x+1,y-2}); 
		v.pb({x-1,y-1}),v.pb({x,y-2});
		v.pb({x-1,y-2});
		cout<<v.size()<<"\n";
		for(auto x:v){
			cout<<x.fi<<' '<<x.se<<"\n";
		}
		return 0;
	}
	v.pb({1,1}),v.pb({1,2}),v.pb({2,1}),v.pb({2,2}),v.pb({2,3}),v.pb({3,2}),v.pb({3,3});
	int x=3,y=3;
	for(int i=1;i<n;i++){
		v.pb({x,y+1}),v.pb({x+1,y}),v.pb({x+1,y+1});
		x++,y++;
	}cout<<v.size()<<"\n";
	for(auto x:v){
		cout<<x.fi<<' '<<x.se<<"\n";
	}
}

小心套子

原题是 CF321D Ciel and Flipboard

我向来是不会思维题的。码完线性基和暴搜就跑了。

我们应该观察到如下性质,记 s i , j s_{i,j} si,j表示这个位置是否变号,那么对于同一行, s i , j ⊕ s i , m ⊕ s i , j + m = 0 s_{i,j}\oplus s_{i,m}\oplus s_{i,j+m}=0 si,jsi,msi,j+m=0 。原因在于, ( i , m ) (i,m) (i,m)一定会被覆盖,而 ( i , j ) (i,j) (i,j) ( i , j + m ) (i,j+m) (i,j+m)恰好会覆盖一个。

因此只要知道左上角的状态就能还原出整个矩阵。感性认识到矩阵操作是线性无关的,那么 2 m × m 2^{m\times m} 2m×m种操作序列恰好对应左上角 2 m × m 2^{m\times m} 2m×m个格子的状态,因此贪心即可。

复杂度 O ( n 2 2 m ) O(n^22^{m}) O(n22m)

remark \text{remark} remark 巧妙的观察。或许这就是oi的魅力吧

#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,m,b[50][50];
ll a[50][50],res=-inf,val[50][2];
ll V(int x,int y){
	return b[x][y]?-a[x][y]:a[x][y];
}
int main(){
	freopen("taozi.in","r",stdin);
	freopen("taozi.out","w",stdout);
	cin>>n,m=(n+1)/2;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
	for(int s=0;s<1<<m;s++){
		ll tot=0;
		for(int i=1;i<=m;i++){
			b[m][i]=s>>i-1&1;
			tot+=V(m,i);
		}for(int i=m+1;i<=n;i++){
			b[m][i]=b[m][i-m]^b[m][m];
			tot+=V(m,i);
		}
		for(int i=1;i<m;i++){
			for(int k=0;k<2;k++){
				b[i][m]=k,b[i+m][m]=k^b[m][m];
				val[i][k]=V(i,m)+V(i+m,m);
				for(int j=1;j<m;j++){
					b[i][j]=0,b[i][j+m]=b[i][j]^b[i][m],b[i+m][j]=b[i][j]^b[m][j],b[i+m][j+m]=b[i][j+m]^b[m][j+m];
					val[i][k]+=abs(V(i,j)+V(i,j+m)+V(i+m,j)+V(i+m,j+m));
				}
			}
			tot+=max(val[i][0],val[i][1]);
		}
		res=max(res,tot);
	}cout<<res;
}

d

原题是 CF765F Souvenirs

数据结构好题。

长话短说,假设当前考虑到 j j j,我们每次找到满足 a j ≤ a i a_j\le a_i ajai的最大的编号 j j j更新答案( a j > a i a_j>a_i aj>ai的情况将值域反转再做一次即可),考虑优化这个过程,假设上一个找到的是 j ′ j' j,那么下一个 j j j如果对答案有贡献的话,显然 a j ′ < a j ≤ a i a_{j'}<a_j\le a_i aj<ajai,并且若 a j − a j ′ ≤ a i − a j a_j-a_{j'}\le a_{i}-a_j ajajaiaj那么这个 a i − a j a_i-a_j aiaj也对答案没有贡献。观察到这样只会跳 log ⁡ \log log次,因此暴力维护即可。复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

#include<bits/stdc++.h>
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
const int N=3e5+5;
int n,m,a[N],b[N],bit[N],tot,ans[N],rt[N];
vector<pair<int,int>> g[N];
void add(int x,int k) {
	for(int i=x;i<=n;i+=i&-i) bit[i]=min(bit[i],k);
}
struct node{
	int l,r,mx;
}t[N<<5];
int upd(int o,int l,int r,int x,int y) {
	int oo=++tot;
	t[oo]=t[o];
	t[oo].mx=max(t[oo].mx,y);
	if(l==r) {
		return oo;
	}
	int mid=(l+r)/2;
	if(x<=mid) t[oo].l=upd(t[oo].l,l,mid,x,y);
	else t[oo].r=upd(t[oo].r,mid+1,r,x,y);
	return oo;
}
int qry(int o,int l,int r,int ql,int qr) {
	if(!o||ql>qr) return 0;
	if(ql<=l&&r<=qr) {
	    return t[o].mx;
	}
	int mid=(l+r)/2;
	if(qr<=mid) return qry(t[o].l,l,mid,ql,qr);
	else if(mid<ql) return qry(t[o].r,mid+1,r,ql,qr);
	else {
		return max(qry(t[o].l,l,mid,ql,qr),qry(t[o].r,mid+1,r,ql,qr));
	}
}
int ask(int x) {
	int tot=inf;
	for(int i=x;i;i-=i&-i) {
		tot=min(tot,bit[i]);
	}
	return tot;
}
int rev(int l) {
	return n-l+1;
}
void work() {
	for(int i=1;i<=n;i++) {
		int l=qry(rt[i-1],0,1e9,a[i],1e9);
		while(l) {
			add(rev(l),a[l]-a[i]);
			l=qry(rt[l-1],0,1e9,a[i],(a[i]+a[l]-1)/2);
		}
		for(auto x:g[i]) {
			ans[x.se]=min(ans[x.se],ask(rev(x.fi)));
		}
		rt[i]=upd(rt[i-1],0,1e9,a[i],i);
	}
	while(tot>0) {
		t[tot]=t[0];
		tot--;
	}
	memset(bit,0x3f,sizeof bit);
}
int main() {
	scanf("%d",&n);memset(ans,0x3f,sizeof ans);memset(bit,0x3f,sizeof bit);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++) {
		int l,r;scanf("%d%d",&l,&r);
		g[r].push_back(make_pair(l,i));
	}
	work();for(int i=1;i<=n;i++) a[i]=1e9-a[i];work();
	for(int i=1;i<=m;i++) {
		printf("%d\n",ans[i]);
	}
}

再来一道类似的题目:[WC2022] 秃子酋长

感觉做法和上一道完全不同啊。

首先问题转化为从 [ l , r ] [l,r] [l,r]的最小值开始,在原序列上走到下一个值的位置,走的总步数。

考虑分治 为啥啥都能分治 。也就是说每次处理跨过 mid \text{mid} mid的询问。

左右分别排序,然后以考虑右边点贡献为例。假设排序后 i i i, j j j相邻( a i < a j a_i<a_j ai<aj),那么有两种情况:

1.1 1.1 1.1 左边存在 a i a_i ai, a j a_j aj之间的数,那么一定是 i i i先走到左边,在左边走走走后回到右边,贡献 i + j i+j i+j
1.2 1.2 1.2 左边不存在 a i a_i ai, a j a_j aj之间的数,那么直接从 i i i走到 j j j,贡献 ∣ j − i ∣ |j-i| ji

对于第一种贡献产生的条件,显然找到左区间最靠右的中间的数,那么询问左端点应该在它的左边。可以用树状数组维护。

最后做一遍扫描线,可以用线段树之类的维护一下前驱后继。

复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n) 。理论上可以吊打根号。

代码咕掉了。

posted @ 2022-11-14 16:35  仰望星空的蚂蚁  阅读(17)  评论(0)    收藏  举报  来源