woj4766-4768
题目很吓人,实际上很水。
从绝对值最小的开始删除,任何时候都联通。
那也就是说两个串的任意后缀都联通。
如果我把一个串删完了,另一个串还是要联通。
所以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;
}
这道题才是今天的好题。
一开始以为是模拟,看完题发现是加括号当场傻掉,静下心来分析性质。
首先,正着的数可以合并,都是正贡献。这样数列就是正负负负正负负正负负负负
而负着的数,我加一个括号,要达到贡献必须放在一个负号后面。那这个负号跟着的数仍然是负的,但后面都是正的。
那我大可以通过括号重叠来让后面正负正负保持我想要的值。所以说,除了第一个数之外,后面都可以是正的。
简单来说就是:
正号不管。
负号+负号可以只负第一个,后面都是正的。这里补充一个证明:
在获得了-a-b之后,我们首先加入左括号-(a-b。
接下来遵循这个原则进行构造:如果为负数,不添加括号。如果为正数,形如-b+c,则将其与左边的负数一起括号包围,形如-(a-(b+c),可以发现贡献为正,且因为正数已经整合,所以下一位必为负数。于是问题重新回到下一位是否为正数的讨论上。我们可以确保任何时候,要么后面没有数字,要么后面有一个负数可以和下一个正数组合。
负号+正号+负号就要选择。如果加,那前两个都要产生负贡献,后面正。不加就和正好不管一样。
根据这个奇怪的后面贡献的性质,我们可以倒着动规。
我们设sufsum表示后缀绝对值和。
那就讨论。
1.不管
2.
①:负负,除了当前是负直接加sufsum(i+1);
②:负正负。两种选择:不加,或者加了负的这两个,获得后面的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;
}
一开始很呆,把火锅写完了回来看(日我火锅树状数组内存开小了当场爆零)
想起来前两天写的一个动规,查询对应的第一个线段。
然后冷静分析,这种没头绪的题一般都有一个可以猜的结论。
我们发现只需要沿着矩形走。
我们发现右边那条边没用。反正我可以先横着走。
而且很明显是被第一个左边的线段更新过来,因为别的都要穿过去不正确。
所以我们维护每条线段的上下点左边对应的线段号,直接预处理。
离散化,造两个假线段一个起点一个终点,记得把起点放到最开头(我调了半小时)
然后每次从来的线段的上下端点选一个更新就可以了。
记得加上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;
}