The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online B Red Black Tree (LCA)

题目链接:

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5807

 

题意:

给一棵n个节点的红黑树,每条边有距离。树上有m个点是红的,每个红点的cost是0,每个黑点的cost是离它最近的红点祖先到它的距离。

q个询问,每次给k个点,现在允许你将树上某个点变红,是这k个点的cost的最大值最小,输出这个值。(数据规模都是10w)

 

思路1:

把每个红点与它父亲断开,这样就有很多棵根节点红色其他节点黑色的树(红根树)。

每次询问k个点,我们只需要考虑它们所在的红根树当中,max(cost)最大和次大的两棵树。

现在要选某个点变红,肯定是选在最大的红根树里。将max(cost)最小化后,和次大的树比一下就行了。

问题转化成,在红根树里选一个点变红,使k'个点的max(cost)最小。那就把k'个点按cost从小到大排序,求k'个后缀LCA就行了。

(可惜比赛的时候一句话写错了,搞半天还以为思路错了)

复杂度:O(n*log(n))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int N=100005;
int red[N];
int redfa[N];
vector<P>link[N];
ll cost[N];
int deep[N];
int sz[N],son[N],fa[N];
void dfs(int u,int _fa,int _red,ll _cost,int _deep){
	fa[u]=_fa;
	deep[u]=_deep;
	cost[u]=_cost;
	redfa[u]=_red;
	sz[u]=1;
	son[u]=-1;
	for (P p:link[u]){
		int v=p.first; ll w=p.second;
		if (v==_fa) continue;
		if (red[v]) dfs(v,u,v,0,_deep+1);
		else dfs(v,u,_red,_cost+w,_deep+1);
		sz[u]+=sz[v];
		if (son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
	}
}
int top[N],id[N],T;
void dfs2(int u,int _fa,int p){
    top[u]=p;
    id[u]=++T;
    if (son[u]!=-1) dfs2(son[u],u,p);
	for (P p:link[u]){
		int v=p.first; ll w=p.second;
        if (v==_fa||v==son[u]) continue;
        dfs2(v,u,v);
    }
}
int getlca(int x,int y){
	if (x==0) return y;
    int f1=top[x],f2=top[y];
    while(f1!=f2){
        if (deep[f1]<deep[f2]) swap(f1,f2),swap(x,y);
        x=fa[f1],f1=top[x];
    }
    if (deep[x]<deep[y]) swap(x,y);
    return y;
}
int stk[N];
int que[N];
ll f[N];
vector<int>VS[N];
int vis[N];
bool cmp(int i,int j){
	return f[i]>f[j];
}
bool cmp2(int i,int j){
	return cost[i]<cost[j];
}
int main(){
	int t; scanf("%d",&t);
	while(t--){
		int n,m,q; scanf("%d%d%d",&n,&m,&q);
		for (int i=1;i<=m;i++){
			int x; scanf("%d",&x);
			red[x]=1;
		}
		for (int i=1;i<n;i++){
			int x,y; ll w; scanf("%d%d%lld",&x,&y,&w);
			link[x].push_back({y,w});
			link[y].push_back({x,w});
		}
		dfs(1,0,1,0,1);
		dfs2(1,0,1);
		for (int T=1;T<=q;T++){
			int num; scanf("%d",&num);
			int top=0;
			for (int i=1;i<=num;i++){
				scanf("%d",&que[i]);
				int _red=redfa[que[i]];
				VS[_red].emplace_back(que[i]);
				if (vis[_red]!=T) stk[++top]=_red,f[_red]=cost[que[i]];
				else f[_red]=max(f[_red],cost[que[i]]);
				vis[_red]=T;
			}
			sort(stk+1,stk+top+1,cmp);
			int _red=stk[1];
			sort(VS[_red].begin(),VS[_red].end(),cmp2);
			int tot=VS[_red].size();
			ll ret = cost[VS[_red][tot - 1]];
			int lca=VS[_red][tot - 1];
			for (int i=tot-1;i>=0;i--){
				lca=getlca(lca,VS[_red][i]);
				ll tmp;
				if (i!=0) tmp=cost[VS[_red][i-1]];
				else tmp=0;
				ret=min(ret,max(tmp,f[_red]-cost[lca]));
			}
			if (top==1) printf("%lld\n",ret);
			else printf("%lld\n",max(ret,f[stk[2]]));
			for (int i=1;i<=top;i++) VS[stk[i]].clear();
		}
		for (int i=1;i<=n;i++){
			link[i].clear();
			red[i]=0;
			vis[i]=0;
		}
	}
}

 

思路2:

二分答案,不满足答案的LCA合并起来再看是否满足。(大家基本是这么做的)

复杂度:O(n*log(1e14)*log(n))     但是树链剖分求LCA实在是太快了,也能过

求LCA的地方还可以转RMQ用ST表做到O(1),就是写起来比较麻烦

(试了下倍增求LCA,T掉了,果然树剖大法好!)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int N=100005;
int red[N];
int redfa[N];
vector<P>link[N];
ll cost[N];
int deep[N];
int que[N];
int sz[N],son[N],fa[N];
void dfs(int u,int _fa,int _red,ll _cost,int _deep){
	fa[u]=_fa;
	deep[u]=_deep;
	cost[u]=_cost;
	redfa[u]=_red;
	sz[u]=1;
	son[u]=-1;
	for (P p:link[u]){
		int v=p.first; ll w=p.second;
		if (v==_fa) continue;
		if (red[v]) dfs(v,u,v,0,_deep+1);
		else dfs(v,u,_red,_cost+w,_deep+1);
		sz[u]+=sz[v];
		if (son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
	}
}
int top[N],id[N],T;
void dfs2(int u,int _fa,int p){
    top[u]=p;
    id[u]=++T;
    if (son[u]!=-1) dfs2(son[u],u,p);
	for (P p:link[u]){
		int v=p.first; ll w=p.second;
        if (v==_fa||v==son[u]) continue;
        dfs2(v,u,v);
    }
}
int getlca(int x,int y){
	if (x==0) return y;
    int f1=top[x],f2=top[y];
    while(f1!=f2){
        if (deep[f1]<deep[f2]) swap(f1,f2),swap(x,y);
        x=fa[f1],f1=top[x];
    }
    if (deep[x]<deep[y]) swap(x,y);
    return y;
}
bool check(ll len,int num){
	int lca=0;
	for (int i=1;i<=num;i++)
		if (cost[que[i]]>len) lca=getlca(lca,que[i]);
	for (int i=1;i<=num;i++)
		if (cost[que[i]]>len){
			if (deep[redfa[que[i]]]>=deep[lca]) return 0;
			if (cost[que[i]]-cost[lca]>len) return 0;
		}
	return 1;
}
int main(){
	int t; scanf("%d",&t);
	while(t--){
		int n,m,q; scanf("%d%d%d",&n,&m,&q);
		for (int i=1;i<=m;i++){
			int x; scanf("%d",&x);
			red[x]=1;
		}
		for (int i=1;i<n;i++){
			int x,y; ll w; scanf("%d%d%lld",&x,&y,&w);
			link[x].push_back({y,w});
			link[y].push_back({x,w});
		}
		dfs(1,0,1,0,1);
		dfs2(1,0,1);
		for (int T=1;T<=q;T++){
			int num; scanf("%d",&num);
			for (int i=1;i<=num;i++) scanf("%d",&que[i]);
			ll l=0,r=1e14;
			while(l<r){
				ll mid=(l+r)/2;
				if (!check(mid,num)) l=mid+1;
				else r=mid;
			}
			printf("%lld\n",l);
		}
		for (int i=1;i<=n;i++){
			link[i].clear();
			red[i]=0;
			deep[i]=0;
		}
	}
}

 

 

BNUOJ 53081 线段树区间合并

题目:

http://www.bnuoj.com/bnuoj/problem_show.php?pid=53081

 

思路:

将‘(’看作1,‘)’看作-1,每次询问:从x开始所有前缀的前缀和均非负,且和为0的最长前缀。

维护线段树节点代表区间的最小前缀和,每次询问处理方式为:

若当前区间[x,i]合并当前节点后最小前缀和非负,则合并,否则不合并且终止查询。

输出合并后的区间内最小前缀对应的长度即可。

 

#include<bits/stdc++.h>
using namespace std;

const int N=500005;

struct line
{
    int len,mi,id,sum;
    line operator+(const line &l)const
    {
        line tmp=*this;
        tmp.len+=l.len;
        if (sum+l.mi<=tmp.mi)
            tmp.mi=sum+l.mi,tmp.id=l.id;
        tmp.sum+=l.sum;
        return tmp;
    }
}a[N*4];

char s[N];

void build(int i,int l,int r)
{
    if (l==r)
    {
        if (s[l]=='(') a[i]={1,1,l,1};
        else a[i]={1,-1,l,-1};
        return;
    }
    int mid=(l+r)/2;
    build(i*2,l,mid);
    build(i*2+1,mid+1,r);
    a[i]=a[i*2]+a[i*2+1];
}
void cg(int i,int l,int r,int x)
{
    if (l==r)
    {
        if (s[l]=='(') a[i]={1,1,l,1};
        else a[i]={1,-1,l,-1};
        return;
    }
    int mid=(l+r)/2;
    if (x<=mid) cg(i*2,l,mid,x);
    else cg(i*2+1,mid+1,r,x);
    a[i]=a[i*2]+a[i*2+1];
}

line L;
bool get(int i,int l,int r,int x)
{
    if (x<=l)
    {
        if ((L+a[i]).mi>=0)
        {
            L=L+a[i];
            return 1;
        }
        else
        {
            if (l==r) return 0;
            int mid=(l+r)/2;
            if (get(i*2,l,mid,x))
                get(i*2+1,mid+1,r,x);
            return 0;
        }
    }
    int mid=(l+r)/2;
    if (x<=mid)
    {
        if (get(i*2,l,mid,x)&&get(i*2+1,mid+1,r,x)) return 1;
        return 0;
    }
    if (get(i*2+1,mid+1,r,x)) return 1;
    return 0;
}
int main()
{
    int t; scanf("%d",&t);
    while(t--)
    {
        int n,q; scanf("%d%d",&n,&q);
        scanf("%s",s+1);
        build(1,1,n);
        while(q--)
        {
            int tp,x; scanf("%d%d",&tp,&x);
            if (tp==1)
            {
                s[x]='('+')'-s[x];
                cg(1,1,n,x);
            }
            else
            {
                L={0,0,x-1,0};
                get(1,1,n,x);
                printf("%d\n",L.id-x+1);
            }
        }
    }
}

Codeforces Packmen Strike Back (二分+dp)

题目链接:

http://codeforces.com/contest/883/problem/D

题意:

给一个字符串,P代表人,每个人往左走到底或者往右走到底,问最多获得多少星号,在这个前提下最少走多少秒收集完。

 

思路:

二分答案,check的时候看前i个人最多可以收集到哪个位置。

转移有三种情况

1. 第i个人往左走,但无法收集完第i-1个人收集的星号,无法让i-1转向

2. 第i个人往左走,可以收集完第i-1个人收集的星号,可以让i-1转向

3. 第i个人往右走,左边都收集完

 

思维难度还是有点大

#include<bits/stdc++.h>
using namespace std;

const int N=1000005;

char s[N];
int sum[N],a[N];
int tot;
int R[N];
int n;

bool have(int r,int l)
{
	l=max(0,l);
	if (l>=r) return 0;
	return sum[r]-sum[l]>0;
}

int f[N];
bool check(int len)
{
	f[0]=0; a[0]=0;
	for (int i=1;i<=tot;i++)
	{
		f[i]=0;
		if (!have(a[i]-len-1,f[i-1])) f[i]=a[i];
		if (i>=2&&!have(a[i]-len-1,f[i-2])) f[i]=max(f[i],a[i-1]+len);
		if (!have(a[i],f[i-1])) f[i]=max(f[i],a[i]+len);
		if (f[i]==0) return 0;
	}
	if (have(n,f[tot])) return 0;
	return 1;
}
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	for (int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1];
		if (s[i]=='*') sum[i]++;
		if (s[i]=='P') a[++tot]=i;
	}
	if (tot==0) printf("0 0");
	else
	{
		if (tot==1)
		{
			int cnt1=0,cnt2=0,dl=0,dr=0;
			for (int i=1;i<=n;i++)
				if (s[i]=='*')
				{
					if (i<a[1]) cnt1++,dl=max(dl,a[1]-i);
					else cnt2++,dr=max(dr,i-a[1]);
				}
			if (cnt1>cnt2) printf("%d %d",cnt1,dl);
			if (cnt1<cnt2) printf("%d %d",cnt2,dr);
			if (cnt1==cnt2) printf("%d %d",cnt1,min(dl,dr));
		}
		else
		{
			int cnt=0;
			for (int i=1;i<=n;i++)
				if (s[i]=='*') cnt++;
			R[n+1]=n+1;
			for (int i=n;i>=1;i--)
				if (s[i]=='*') R[i]=i;
				else R[i]=R[i+1];
			int l=0,r=n;
			while(l<r)
			{
				int mid=(l+r)/2;
				if (!check(mid)) l=mid+1;
				else r=mid;
			}
			printf("%d %d",cnt,l);
		}
	}
}

HDU5956 The Elder 下凸壳的维护

题意:

一棵树,1为根。每个点只能走向祖先,时间为距离的平方,另外走到祖先后会停留P单位时间。求每个点到达根的最小时间的最大值。

 

思路:

显然dp方程为:f[i]=(d[i]-d[j])^2+P+f[j]  ( j 是 i 的祖先)

转化成向量点积形式:f[i]=d[i]^2+P+(1 , 2*d[i] ) * ( f[j]+d[j]^2 , -d[j] )

注意到深度越大,向量(1 , 2*d[i] )会逆时针旋转

于是维护点集i的所有祖先的  ( f[j]+d[j]^2 , -d[j] ) 的下凸壳,就能求出f[i]的最小值

注意到向量( f[j]+d[j]^2 , -d[j] )只会添加在下凸壳右边,于是只要用个队列来保存就可以了

查询的时候只要一直扔掉左边的点,找到最大值为止,然后退出当前dfs的时候加回来就可以了

如果一个个保存删掉的点,再一个个加回来的话,在菊花图的情况下复杂度就会变成O(n^2),事实上目前网上的题解都是这么做的,估计出题人也没意识到复杂度的问题,因为这看起来很像O(n)。

我的做法是每次dfs记录当前队列的左端点,和右端点,操作完后,事实上只是替换了数组里的tail位置的元素,把head和tail还有这个元素事先保存下来,最后恢复head,tail和这个元素就可以了,这样复杂度就是完美的O(n)了。

也不知道这种做法叫什么,毕竟自己乱想出来的,就叫回溯队列好了233

后来一想这个做法还是n方。。。因为每次head和tail的删除操作都可能是O(n)的,所以要二分查找head和tail分别应该删到哪,这样算法复杂度就是O(nlogn)了

这里如果不用斜率,而把两边的dx都乘到对面去的话是有问题的,因为f[j]+d[j]^2的规模是1e14,2*d[i]的规模是1e7,乘起来理论上会爆,然而数据好像还是没卡掉这种做法

 

代码1:O(n^2)做法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
struct P
{
	ll x,y;
	ll operator*(const P &b)const
	{
		return x*b.x+y*b.y;
	}
}stk[N];
int n,p;
int head,tail;
vector<P>link[N];
ll f[N],d[N];
long double slope(P a,P b)
{
	long double tmp=1.0;
	return tmp*(b.y-a.y)/(b.x-a.x);
}
void dfs(int u,int fa,int deep)
{
	int phead=head,ptail=tail;
	d[u]=deep;
	if (u==1) f[u]=-p;
	else
	{
		P tmp={1,2*d[u]};
		while(tail-head+1>=2&&stk[head]*tmp>=stk[head+1]*tmp) head++;
		f[u]=d[u]*d[u]+p+stk[head]*tmp;
	}
	P tmp={f[u]+d[u]*d[u],-d[u]};
	while(tail-head+1>=2&&
		slope(stk[tail-1],stk[tail])>=slope(stk[tail],tmp)) tail--;
	P mem=stk[tail+1];
	stk[++tail]=tmp;

	for (int i=0;i<link[u].size();i++)
	{
		int v=link[u][i].x;
		if (v==fa) continue;
		dfs(v,u,deep+link[u][i].y);
	}
	tail--;
	stk[tail+1]=mem;
	head=phead; tail=ptail;
}
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&p);
		for (int i=1;i<n;i++)
		{
			ll x,y,z;
			scanf("%d%d%lld",&x,&y,&z);
			if (z<0) n/=0;
			link[x].push_back({y,z});
		}
		head=1; tail=0;
		dfs(1,1,0);
		ll ans=0;
		for (int i=1;i<=n;i++) ans=max(ans,f[i]);
		printf("%lld\n",ans);

		for (int i=1;i<=n;i++) link[i].clear();
	}
}

 

代码2:O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
struct P
{
    ll x,y;
    ll operator*(const P &b)const
    {
        return x*b.x+y*b.y;
    }
}stk[N];
int n,p;
int head,tail;
vector<P>link[N];
ll f[N],d[N];
long double slope(P a,P b)
{
    long double tmp=1.0;
    return tmp*(b.y-a.y)/(b.x-a.x);
}
int gethead(P v)
{
	int l=head,r=tail;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (stk[mid]*v>=stk[mid+1]*v) l=mid+1;
		else r=mid;
	}
	return l;
}
int gettail(P v)
{
	int l=head,r=tail;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (slope(stk[mid],stk[mid+1])<slope(stk[mid+1],v)) l=mid+1;
		else r=mid;
	}
	return l;
}
void dfs(int u,int fa,int deep)
{
    int phead=head,ptail=tail;
    d[u]=deep;
    if (u==1) f[u]=-p;
    else
    {
        P tmp={1,2*d[u]};
        head=gethead(tmp);

        //while(tail-head+1>=2&&stk[head]*tmp>=stk[head+1]*tmp) head++;
        f[u]=d[u]*d[u]+p+stk[head]*tmp;
    }
    P tmp={f[u]+d[u]*d[u],-d[u]};
    tail=gettail(tmp);
//    while(tail-head+1>=2&&
//        slope(stk[tail-1],stk[tail])>=slope(stk[tail],tmp)) tail--;
    P mem=stk[tail+1];
    stk[++tail]=tmp;

    for (int i=0;i<link[u].size();i++)
    {
        int v=link[u][i].x;
        if (v==fa) continue;
        dfs(v,u,deep+link[u][i].y);
    }
    tail--;
    stk[tail+1]=mem;
    head=phead; tail=ptail;
}
int main()
{
    int t; scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&p);
        for (int i=1;i<n;i++)
        {
            ll x,y,z;
            scanf("%d%d%lld",&x,&y,&z);
            link[x].push_back({y,z});
        }
        head=1; tail=0;
        dfs(1,1,0);
        ll ans=0;
        for (int i=1;i<=n;i++) ans=max(ans,f[i]);
        printf("%lld\n",ans);

        for (int i=1;i<=n;i++) link[i].clear();
    }
}

FZOJ 2274 二分+有源汇上下界最小流

http://acm.fzu.edu.cn/problem.php?pid=2274

二分差值,求出最小的存在上下界可行流的答案,再跑一次上下界最小流。

这题恶心的地方在于建图从班级流向寝室会TLE,从寝室流向班级就会快很多。

因为只有三层边,dinic里增广次数只有几次,每次dinic复杂度几乎是O(n+m)的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=200005,INF=0x3f3f3f3f;
const int mxN=200005,mxM=500005;
int n,m,k;
int du[N];
int ra[N],rb[N],rc[N],rd[N];
int in[N];
int S,T,SS,TT;
int inflow;

struct graph
{
	int S,T;
    int base[mxN],vec[2*mxM],pre[2*mxM],tot;
    int c[2*mxM];
    int d[mxN],q[mxN];
    bool vis[mxN];
    void init()
    {
    	memset(base,0,sizeof(base));
        tot=1;
    }
    void link(int x,int y,int z)
    {
        vec[++tot]=y; pre[tot]=base[x]; base[x]=tot; c[tot]=z;
        vec[++tot]=x; pre[tot]=base[y]; base[y]=tot; c[tot]=0;
    }
    bool bfs()
    {
        int head=0,tail=0;
        memset(d,-1,sizeof(d));
        d[S]=0;
        q[++tail]=S;
        while(head<tail)
        {
            head++;
            int u=q[head];
            for (int now=base[u];now;now=pre[now])
            {
                int v=vec[now];
                if (d[v]==-1&&c[now]>0)
                {
                    d[v]=d[u]+1;
                    q[++tail]=v;
                    if (v==T) return 1;
                }
            }
        }
        return 0;
    }
    int dfs(int u,int flow)
    {
        int r=0;
        if (u==T) return flow;
        for (int now=base[u];now&&r<flow;now=pre[now])
        {
            int v=vec[now];
            if (c[now]>0&&d[v]==d[u]+1)
            {
                int x=min(c[now],flow-r);
                x=dfs(v,x);
                r+=x;
                c[now]-=x;
                c[now^1]+=x;
            }
        }
        if (!r)d[u]=-2;
        return r;
    }
    int dinic()
    {
        int ans=0;
        while(bfs())
            ans+=dfs(S,INF);
        return ans;
    }
}G;

bool pwork(int dif)
{
	G.init();
	memset(in,0,sizeof(in));

	for (int i=1;i<=k;i++)
		G.link(n+rd[i],rc[i],1);

	S=0,T=n+m+3;
	SS=T-2; TT=T-1;
	for (int i=1;i<=n;i++)
	{
		int l=(du[i]-dif+1)/2;
		int r=(du[i]+dif)/2;
		if (l<0) l=0;
		if (r>du[i]) r=du[i];
		if (l>r) return 0;
		G.link(i,TT,r-l);
		in[i]-=l;
		in[TT]+=l;
	}

	for (int i=1;i<=m;i++)
	{
		int l=du[n+i]-rb[i];
		int r=ra[i];
		if (r>du[n+i]) r=du[n+i];
		if (l<0) l=0;
		G.link(SS,n+i,r-l);
		in[SS]-=l;
		in[n+i]+=l;
	}

	inflow=0;

	for (int i=1;i<T;i++)
	{
		if (in[i]>0) G.link(S,i,in[i]),inflow+=in[i];
		if (in[i]<0) G.link(i,T,-in[i]);
	}
	return 1;
}
int maxflow;
bool check(int dif)
{
	if (!pwork(dif)) return 0;
	G.S=S;  G.T=T;
    G.link(TT,SS,INF);
    int tmp=G.dinic();
    return tmp==inflow;
}
int get(int dif)
{
	if (!pwork(dif)) return 0;
	G.S=S;  G.T=T;
	G.dinic();
    G.link(TT,SS,INF);
    return G.dinic();
}

int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		memset(du,0,sizeof(du));
		for (int i=1;i<=m;i++)
			scanf("%d%d",&ra[i],&rb[i]);
		scanf("%d",&k);
		for (int i=1;i<=k;i++)
		{
			scanf("%d%d",&rc[i],&rd[i]);
			du[rc[i]]++;
			du[n+rd[i]]++;
		}

        int l=0,r=0;
        for (int i=1;i<=n;i++) r=max(r,du[i]);
        while(l<r)
		{
			int mid=(l+r)/2;
			if (!check(mid)) l=mid+1;
			else r=mid;
		}
		printf("%d %d\n",r,get(r));
	}
	return 0;
}