【csp-s】20191024【结论】【动规】【线段树】

woj4766-4768

4766tom

题目很吓人,实际上很水。

从绝对值最小的开始删除,任何时候都联通。

那也就是说两个串的任意后缀都联通。

如果我把一个串删完了,另一个串还是要联通。

所以a和b实际上各自是一个联通块。

那随便找一个地方分开然后dfs。根节点明显要用最大的。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int n,x,y;
int first[100003],nxt[200003],to[200003],tot;
int size[100003],sta,stb;
void add(int a,int b){
	nxt[++tot]=first[a];first[a]=tot;to[tot]=b;
}
void dfs(int u,int fa){
	size[u]=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(v==fa)continue;
		dfs(v,u);size[u]+=size[v];
	}if(size[u]==x){
		sta=u,stb=fa;
	}else if(size[u]==y){
		sta=fa;stb=u;
	}
}int ans[100003];int id[100003];
void dfsa(int u,int fa){
	ans[u]=x--;id[u]=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(v==fa||v==stb)continue;
		dfsa(v,u);
	}
}
void dfsb(int u,int fa){
	ans[u]=y--;id[u]=-1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(v==fa||v==sta)continue;
		dfsb(v,u);
	}
}
signed main(){
	n=in;x=in;y=in;for(int i=1;i<n;i++){
		int a=in;int b=in;add(a,b);add(b,a);
	}
	dfs(1,0);
	if(!sta){
		cout<<"-1";return 0;
	}
	dfsa(sta,0);
	dfsb(stb,0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]*id[i]<<" ";
	}
	return 0;
}

4767jerry

这道题才是今天的好题。

一开始以为是模拟,看完题发现是加括号当场傻掉,静下心来分析性质。

首先,正着的数可以合并,都是正贡献。这样数列就是正负负负正负负正负负负负

而负着的数,我加一个括号,要达到贡献必须放在一个负号后面。那这个负号跟着的数仍然是负的,但后面都是正的。

那我大可以通过括号重叠来让后面正负正负保持我想要的值。所以说,除了第一个数之外,后面都可以是正的。

简单来说就是:

正号不管。

负号+负号可以只负第一个,后面都是正的。这里补充一个证明:

在获得了-a-b之后,我们首先加入左括号-(a-b。

接下来遵循这个原则进行构造:如果为负数,不添加括号。如果为正数,形如-b+c,则将其与左边的负数一起括号包围,形如-(a-(b+c),可以发现贡献为正,且因为正数已经整合,所以下一位必为负数。于是问题重新回到下一位是否为正数的讨论上。我们可以确保任何时候,要么后面没有数字,要么后面有一个负数可以和下一个正数组合。

负号+正号+负号就要选择。如果加,那前两个都要产生负贡献,后面正。不加就和正好不管一样。

根据这个奇怪的后面贡献的性质,我们可以倒着动规。

我们设sufsum表示后缀绝对值和。

那就讨论。

1.a[i]>0不管

2.a[i]<0

①:a[i+1]<0负负,除了当前是负直接加sufsum(i+1);

②:a[i+1]>0负正负。两种选择:不加,或者加了负的这两个,获得后面的sufsum(i+2)。

如此。

//太多括号没用。括号意味着取反。 
//一个负号后面有两种。一种是舍弃下一个,其它都为正。反正第三位开始所有数字都可以取正。 
//反着动规即可 
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int t,n;
char ch[500003];
int f[200003];
int a[200003];int cnt;int b[200003];
int sum[200003];
signed main(){
	t=in;
	while(t--){
		n=in;for(int i=1;i<=n;i++)f[i]=0;cnt=0;
		for(int i=1;i<=n;i++)a[i]=in;a[0]=-1;
		for(int i=1;i<=n;i++){
			if(a[i]<0)b[++cnt]=a[i];
			else if(a[i-1]<0)b[++cnt]=a[i];
			else b[cnt]+=a[i];
		}
		//for(int i=1;i<=cnt;i++)cout<<b[i]<<" ";cout<<endl;
		sum[cnt+1]=0;
		for(int i=cnt;i>=1;i--){
			sum[i]=sum[i+1];if(b[i]>=0)sum[i]+=b[i];else sum[i]-=b[i];
		}
		//for(int i=1;i<=cnt;i++)cout<<sum[i]<<" ";cout<<endl;
		f[cnt]=b[cnt];
		for(int i=cnt-1;i>=1;i--){
			 if(b[i]>=0)f[i]=b[i]+f[i+1];
			 else{
			 	if(b[i+1]<0)f[i]=b[i]+sum[i+1];
			 	else{
			 		f[i]=max(f[i+1]+b[i],b[i]-b[i+1]+sum[i+2]);
				 }
			 }
		}
		cout<<f[1]<<'\n';
	}
	return 0;
} 

4768speike

一开始很呆,把火锅写完了回来看(日我火锅树状数组内存开小了当场爆零)

想起来前两天写的一个动规,查询对应的第一个线段。

然后冷静分析,这种没头绪的题一般都有一个可以猜的结论。

我们发现只需要沿着矩形走。

我们发现右边那条边没用。反正我可以先横着走。

而且很明显是被第一个左边的线段更新过来,因为别的都要穿过去不正确。

所以我们维护每条线段的上下点左边对应的线段号,直接预处理。

离散化,造两个假线段一个起点一个终点,记得把起点放到最开头(我调了半小时)

然后每次从来的线段的上下端点选一个更新就可以了。

记得加上x的贡献。

//感性理解:沿着线段走总是没错的,,反正只需要算y轴的贡献。总不能横着走回去。
//线段sort,把起点的假线段放在第一个。然后动态规划。 
#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
struct node{
	int l,r,tag,key;
}t[5000003];
int pre[1000003][2];
int f[1000003][2];
int n,end;
struct Line{
	int down,up,x;
}line[1000003];int lcnt;
int bb[4000003],bcnt,len;
bool linecm(Line a,Line b){
	if(a.x!=b.x)return a.x<b.x;
	if(a.down!=b.down)return a.down<b.down;
	return a.up<b.up;
}
inline void build(int u,int l,int r){
	t[u].l=l;t[u].r=r;
	if(l==r){
		return;
	}int mid=(l+r)>>1;
	build(u*2,l,mid);build(u*2+1,mid+1,r);
}
inline void pushdown(int u){
	if(t[u].tag){
		t[u*2].tag=t[u*2+1].tag=t[u].tag;
		t[u*2].key=t[u*2+1].key=t[u].tag;
		t[u].tag=0;
	}
}
inline void modify(int u,int ql,int qr,int key){
	if(t[u].r<ql||t[u].l>qr)return;
	if(ql<=t[u].l&&t[u].r<=qr){
		t[u].tag=t[u].key=key;return;
	}pushdown(u);
	modify(u*2,ql,qr,key);modify(u*2+1,ql,qr,key);
}
inline int query(int u,int pos){
	if(t[u].l==t[u].r&&t[u].r==pos)return t[u].key;
	pushdown(u);
	int mid=(t[u].l+t[u].r)>>1;
	if(pos<=mid)return query(u*2,pos);else return query(u*2+1,pos);
}
signed main(){
	//freopen("speike.in","r",stdin);
	//freopen("speike.out","w",stdout);
	memset(f,0x3f,sizeof(f));
	n=in;end=in;int ri=(end>=0)?1:-1;end*=ri;
	line[++lcnt]={0,0,0};line[++lcnt]={0,0,end};//bb[++bcnt]=0;
	for(register int i=1;i<=n;i++){
		int a=in;int b=in;int c=in;int d=in;
		a*=ri;c*=ri;
		if(b<d)swap(b,d);bb[++bcnt]=b;bb[++bcnt]=d;
		line[++lcnt]={d,b,a};//line[++lcnt]={d,b,c};
	}
	bb[++bcnt]=0;
	sort(line+1,line+lcnt+1,linecm);sort(bb+1,bb+bcnt+1);
	len=unique(bb+1,bb+bcnt+1)-bb-1;
	for(register int i=1;i<=lcnt;i++){
		line[i].down=lower_bound(bb+1,bb+len+1,line[i].down)-bb;
		line[i].up=lower_bound(bb+1,bb+len+1,line[i].up)-bb;
	}
	//for(register int i=1;i<=5;i++)cout<<line[i].down<<" "<<line[i].up<<endl;
	for(register int i=2;i<=lcnt;i++){
		if(line[i].x)break;if(!bb[line[i].down]&&!bb[line[i].up]){swap(line[i],line[1]);break;}
	}
	build(1,1,len);
	for(register int i=1;i<=lcnt;i++){
		pre[i][0]=query(1,line[i].down);pre[i][1]=query(1,line[i].up);
		modify(1,line[i].down,line[i].up,i);
	}
	//for(register int i=58;i<=63;i++)cout<<pre[i][0]<<" "<<pre[i][1]<<endl;
	f[1][0]=f[1][1]=0;
	for(register int i=2;i<=lcnt;i++){
		int p=max(1,pre[i][0]);
		f[i][0]=min(f[p][0]+abs(bb[line[i].down]-bb[line[p].down]),f[p][1]+abs(bb[line[i].down]-bb[line[p].up]));
		p=max(1,pre[i][1]);
		f[i][1]=min(f[p][0]+abs(bb[line[i].up]-bb[line[p].down]),f[p][1]+abs(bb[line[i].up]-bb[line[p].up]));
	}
	cout<<f[lcnt][0]+end;
	return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值