noip模拟15

这次再次刷新排名下限,遭遇了严重挂分后掉出了前20

Wm3Jvq.png

\(t1\) 因为没选对编译器+精度损失挂40分暴力,\(t2\) 因为更智障的错误挂 55 分……

Wm8tWd.png

int 类型的 dfs1 调用以后没赋值暴毙……

cyh 巨佬暴切 \(t3\) \(rk1\),ys 巨佬暴切 \(t2\)

考场

没睡醒看 \(t1\),打了个暴力找规律,试图用欧拉函数,浪费了一个小时
\(t2\) 没思路,再看 \(t3\),发现好像胡了一个比较正确但是较为难写的做法,于是赶紧回去把 \(t2\) 暴力打完连大样例都没测直接开始打 \(t3\),还有一个小时的时候打完,一遍过样例,开始很自信地以为 A 掉了这道题
发现 \(t3\) 没有大样例,稳妥一点还是写个对拍把
写完没两下就挂了,开始紧张调代码,越调越紧张,直到最后考试结束
于是整场考试唯一得分是对拍打的暴力……


A. 夜莺与玫瑰

又是一个数学神仙题

首先转化细想,只考虑斜率>0的函数,最后 \(ans * 2+n+m\) 即可
对于每种解析式的函数,点阵会有若干点落在上面,那么只统计这条直线上最后一个点即可
对于每个斜率的函数,可以由 \((a,b)\) 唯一表示,其中 \(gcd(a,b)=1\)

那么总数即是 \((n-a)(m-b)-max(n-2a,0) * max(n-2b,0)\)
理解一下:总的点数减去中间部分的点

那么最后的答案即为

\[\sum_{a=1}^{n}\sum_{b=1}^{n}[gcd(a,b)==1](n-a)(m-b)-max(n-2a,0)max(m-2b,0) \]

观察后面的 max 很不舒服,发现当 \(a\le n\) 的时候才会有值,所以继续化简:

\[\sum_{a=1}^{n}\sum_{b=1}^{m}[gcd(a,b)==1](n-a)(m-b)-\sum_{a=1}^{\frac{n}{2}}\sum_{b=1}^{\frac{m}{2}} (n-2a)(m-2b) \]

然后发现这个 \(b\)\(gcd\) 很烦人,思考一下发现 \((n-a)*m\) 这个东西出现个数只和满足条件的 \(b\) 的个数有关,所以处理数组 \(tot[a][m]\) 表示 \([1,m]\) 区间内与 \(a\) 互质的数的个数

后面 \((n-a)b\) 表述出来相当于求与 \(a\) 互质的所有数的和,类似地处理一个 \(sum\) 数组

\[\sum_{a=1}^{n}(n-a)(m\times tot[a][m]-sum[a][m])-\sum_{a=1}^{\frac{n}{2}} (n-2a)(m\times tot[a][\frac{m}{2}]-2sum[a][\frac{m}{2}]) \]

于是对于每组询问 \(O(n)\) 回答即可

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=4015;
const int MAX=4001;
const int mod=(1<<30);
int sum[maxn][maxn],n,m,t;
short g[maxn][maxn],tot[maxn][maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();	
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
int main(){
//	printf("%d\n",sizeof(int));
	for(int i=1;i<=MAX;i++){
		g[i][i]=g[i][0]=g[0][i]=i;
		for(int j=1;j<i;j++){
			g[i][j]=g[j][i]=g[j][i%j];
		}
	}
	for(int i=1;i<=MAX;i++){
		for(int j=1;j<=MAX;j++){
			tot[i][j]=tot[i][j-1];
			sum[i][j]=sum[i][j-1];
			if(g[i][j]==1){
				tot[i][j]++;
				sum[i][j]=(sum[i][j]+j)%mod;
			}
		}
	}
	t=read();
	while(t--){
		n=read();
		m=read();
		long long ans=0;
		for(int i=1;i<n;i++){
			ans=(ans+(n-i)*(tot[i][m]*m%mod-sum[i][m])%mod)%mod;
			if(i*2<n)ans=ans-(n-2*i)*(m*tot[i][m/2]%mod-2*sum[i][m/2]%mod)%mod;
		}
		ans=((ans*2%mod+n+m)%mod+mod)%mod;
		printf("%lld\n",ans);
	}
	return 0;
}

B. 影子

可以统计过每个点的以这个点为最小值时的贡献,可以发现,如果按照点权排序,从小到大枚举点,统计完路径后,可以将点删除,将连通块裂为数个连通块

但是发现分裂的操作非常不好维护,可以转化思维,将分裂变为合并,合并操作就可以用并查集轻松维护了
每次合并相当于求连通块内的直径乘以点权统计贡献(如果直径不过当前点也没有关系, 因为这条直径一定已经在某个点权更大的点上统计过贡献,那么这个点不会产生影响)
并查集维护连通性、连通块直径长度、端点即可,用维护直径的经典套路合并

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=3e5+5;
const int maxm=6e5+5;
int tt,n,m,cnt,hd[maxn],f[maxn],fa[maxn],dep[maxn],dis[maxn],d[maxn],ans,s[maxn],t[maxn],x,y,w,tp[maxn],son[maxn],siz[maxn],b[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();	
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct P{
	int val,id;	
}a[maxn];
bool cmp(P a,P b){
	return a.val>b.val;
}
struct Edge{
	int nxt,to,val;
}edge[maxm];
void add(int u,int v,int w){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	edge[cnt].val=w;
	hd[u]=cnt;
	return ;
}
void dfs(int u){
	siz[u]=1;
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u])continue;
		dep[v]=dep[u]+1;
		dis[v]=dis[u]+edge[i].val;
		fa[v]=u;
		dfs(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
	return ;
}
void dfs1(int u,int top){
	tp[u]=top;
	if(!son[u])return ;
	dfs1(son[u],top);
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u]||v==son[u])continue;
		dfs1(v,v);
	}
	return ;
}
int find(int x){
	return f[x]==x?x:f[x]=find(f[x]);
}
int lca(int x,int y){
	while(tp[x]!=tp[y]){
		if(dep[tp[x]]<dep[tp[y]])swap(x,y);
		x=fa[tp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
int calc(int x,int y){
	int k=lca(x,y);
	return dis[x]+dis[y]-2*dis[k];
}
void merge(int x,int y){
	int mx=0,ss,tt;
	if(d[x]>mx){
		mx=d[x];
		ss=s[x];
		tt=t[x];
	}
	if(d[y]>mx){
		mx=d[y];
		ss=s[y];
		tt=t[y];
	}
	if((w=calc(s[x],s[y]))>mx){
		mx=w;
		ss=s[x];
		tt=s[y];
	}
	if((w=calc(s[x],t[y]))>mx){
		mx=w;
		ss=s[x];
		tt=t[y];
	}
	if((w=calc(t[x],s[y]))>mx){
		mx=w;
		ss=t[x];
		tt=s[y];
	}
	if((w=calc(t[x],t[y]))>mx){
		mx=w;
		ss=t[x];
		tt=t[y];
	}
	s[x]=ss;
	t[x]=tt;
	d[x]=mx;
	f[y]=x;
	return ;
}
signed main(){
//freopen("1.in","r",stdin);
	tt=read();
	while(tt--){
		ans=0;
		memset(hd,0,sizeof hd);
		cnt=0;
		memset(fa,0,sizeof fa);
		memset(son,0,sizeof son);
		
		n=read();
		for(int i=1;i<=n;i++){
			b[i]=a[i].val=read();
			a[i].id=i;
		}
		sort(a+1,a+n+1,cmp);
		for(int i=1;i<=n-1;i++){
			x=read();
			y=read();
			w=read();
			add(x,y,w);
			add(y,x,w);
		}
//		cout<<"hhh "<<endl;
//		cout<<"hhh ";
		dfs(1);
//		cout<<"hhh "<<endl;
		dfs1(1,1);
//		cout<<"hhh "<<endl;
		sort(a+1,a+n+1,cmp);
		for(int i=1;i<=n;i++){
			f[i]=i;
			s[i]=t[i]=i;
			d[i]=0;
		}
//		cout<<calc(2,3)<<endl;
		for(int j=1;j<=n;j++){
			int u=a[j].id;
			for(int i=hd[u];i;i=edge[i].nxt){
				int v=edge[i].to;
				if(find(u)!=find(v)&&b[v]>=b[u])merge(find(u),find(v));
			}
			ans=max(ans,d[find(u)]*a[j].val);
		}
		cout<<ans<<endl;
	}
	return 0;
}

/*
1
3
1 2 3
1 2 1
1 3 2
*/

C. 玫瑰花精

考场上看出来是线段树,只不过我的思路有亿点点奇怪
可以把所有花精位置丢进一个 \(set\) 里,每次插入后可以快速找到前一个和后一个的位置
用线段树动态维护里所有花精最远的位置,每次插入一个花精后,对于前后分别加一个公差为1或-1的等差数列(这个操作由于过于阴间,特别难写……)
考场调了1小时+ 也没调处来
考后改正智障错误自信提交,发现T了,发现 \(set\) 时间复杂度太高,于是又手写了一棵权值线段树,最后搞得又臭又长……
正解是 cyh 巨佬的写法,线段树像维护山海经一样维护最长的区间,并动态维护左右端点,貌似特别好写……
update: 并不是 \(set\) 常数大,而是把 \(set\) 放进了 \(lower\_bound\) 里……

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
const int inf=0x3f3f3f3f;
int n,m,op,x,pos[maxn],ll,rr,wz[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
set<int>s;
struct Node{
	int fi,se;
	Node(){}
	Node(int x,int y):fi(x),se(y){}
};
struct Node1{
	int fi,se;
	Node1(){}
	Node1(int x,int y):fi(x),se(y){}
};
struct Seg{
	int l,r;
	Node mx;
	Node1 lazy;
}t[maxn*4];
#define ls t[p<<1]
#define rs t[p<<1|1]

void update(int p){
	if(ls.mx.fi>=rs.mx.fi)t[p].mx=ls.mx;
	else t[p].mx=rs.mx;
	return ;
}
void build(int p,int l,int r){
	t[p].l=l;
	t[p].r=r;
	t[p].lazy=Node1(0,0);
	if(l==r){
		t[p].mx=Node(0,l);
		return ;
	}
	int mid=l+r>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	update(p);
	return ;
}
void spread(int p){
	Node1 k=t[p].lazy;
	ls.lazy=k;
	rs.lazy=Node1(k.fi+(ls.r-ls.l+1)*k.se,k.se);
	if(t[p].lazy.se==1){
		ls.mx=Node(k.fi+ls.r-ls.l,ls.r);
		rs.mx=Node(rs.lazy.fi+rs.r-rs.l,rs.r);
	}
	else{
		ls.mx=Node(k.fi,ls.l);
		rs.mx=Node(rs.lazy.fi,rs.l);
	}
	t[p].lazy=Node1(0,0);
	return ;
}
void change(int p,int l,int r,int val,int op,int ori){
	if(l>r)return ;
	if(t[p].l>=l&&t[p].r<=r){
		t[p].lazy=Node1(val,op);
		if(op==1){
			t[p].mx=Node(val+t[p].r-t[p].l,t[p].r);
		}
		else{
			t[p].mx=Node(val,t[p].l);
		}
		return ;
	}
	if(t[p].lazy.se!=0)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid)change(p<<1,l,r,val,op,ori);
	if(r>mid)change(p<<1|1,l,r,ori+op*(mid-l+1),op,ori);
	update(p);
	return ;
}
namespace S{
	struct Seg1{
		int l,r,mn,mx;
	}t1[maxn*4];
	void update1(int p){
		t1[p].mn=min(t1[p<<1].mn,t1[p<<1|1].mn);
		t1[p].mx=max(t1[p<<1].mx,t1[p<<1|1].mx);
		return ;
	}
	void build1(int p,int l,int r){
		t1[p].l=l;
		t1[p].r=r;
		if(l==r){
			wz[l]=p;
			t1[p].mn=0x3f3f3f3f;
			return ;
		}
		int mid=l+r>>1;
		build1(p<<1,l,mid);
		build1(p<<1|1,mid+1,r);
		update1(p);
		return ;
	}
	void change1(int p,int pos,int op){
		if(t1[p].l==t1[p].r&&t1[p].l==pos){
			if(op==1)t1[p].mn=t1[p].mx=pos;
			else t1[p].mn=inf,t1[p].mx=0;
			return ;
		}
		int mid=t1[p].l+t1[p].r>>1;
		if(pos<=mid)change1(p<<1,pos,op);
		else change1(p<<1|1,pos,op);
		update1(p);
		return ;
	}
	void ask(int pp){
		int p=wz[pp];
		while(p>>1){
			if(((p>>1)<<1)==p){
				if(rr==inf)rr=t1[(p>>1)<<1|1].mn;
			}
			else{
				if(!ll)ll=t1[(p>>1)<<1].mx;
			}
			p>>=1;
		}
		return ;
	}
}
int main(){
//	freopen("shuju.in","r",stdin);
//	freopen("my.out","w",stdout);
	n=read();
	m=read();
	build(1,1,n);
	S::build1(1,1,n);
	for(int i=1;i<=m;i++){
		op=read();
		x=read();
		if(op==1){
			pos[x]=t[1].mx.se;
			S::change1(1,pos[x],1);
//			set<int>::iterator it=s.insert(pos[x]).first;
			ll=0;
			rr=inf;
			S::ask(pos[x]);
			int l;
			//set<int>::iterator it1=it;
			if(!ll)l=1;
			else l=(ll+pos[x]+1)>>1;
			if(l!=pos[x])change(1,l,pos[x],(pos[x]-l),-1,pos[x]-l);
			printf("%d\n",pos[x]);
//			it++;
			change(1,pos[x],rr!=inf?((pos[x]+rr)>>1):n,0,1,0);
		}
		else{
			ll=0;
			rr=inf;
			S::ask(pos[x]);
		//	set<int>::iterator it=upper_bound(s.begin(),s.end(),pos[x]);
			//set<int>::iterator it1=it;it1--;//lower_bound(s.begin(),s.end(),pos[x]);
			if((!ll)&&(rr==inf)){
				build(1,1,n);
			}
			else{
				int l,r;
				if(!ll){
					r=rr-1;
					change(1,1,r,r,-1,r);
				}
				else if(rr==inf){
					l=ll+1;
					change(1,l,n,1,1,1);
				}
				else{
					l=ll+1;
					r=rr-1;
					int mid=l+r>>1;
					change(1,l,mid,1,1,1);
					change(1,mid+1,r,(r-mid),-1,r-mid);
				}
			}
			S::change1(1,pos[x],-1);
			//s.erase(pos[x]);
		}
	}
	return 0;
}

/*
7 11
1 15
1 123123
1 3
1 5
2 123123
2 15
1 21
2 3
1 6
1 7
1 8


1
7
4
2
7
4
1
3
*/
posted @ 2021-07-15 12:04  y_cx  阅读(80)  评论(2编辑  收藏  举报