hdu 5421 Victor and String (支持双向添加的回文树)

题目:

http://acm.hdu.edu.cn/showproblem.php?pid=5421

 

思路:

维护左右两端的节点指针,稍微修改一下就好了

VictorWonder太强啦)

 

 

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

struct Palindromic_Tree{
    static const int maxN=200005,CN=26;
    int ch[maxN][CN];
    int fail[maxN],len[maxN],num[maxN],cnt[maxN];
    int str[maxN],L,R;
    int last[2],tot;
    ll sum;
    int newnode(int _len){
        tot++;
        for (int i=0;i<CN;i++) ch[tot][i]=0;
        len[tot]=_len;
        num[tot]=cnt[tot]=0;
        return tot;
    }
    void init(){
        tot=-1; sum=0;
        newnode(0); newnode(-1);
        fail[0]=1; fail[1]=0;
        last[0]=last[1]=0;
        L=maxN/2; R=L-1;
        str[L-1]=str[R+1]=-1;
    }
    int getfail(int x,bool right){
        if (right) 
            while(str[R-1-len[x]]!=str[R]) 
                x=fail[x];
        else
            while(str[L+1+len[x]]!=str[L]) 
                x=fail[x];
        return x;
    }
    void insert(int w,bool right){
        if (right) str[++R]=w,str[R+1]=-1;
        else str[--L]=w,str[L-1]=-1;
        int u=getfail(last[right],right);
        if (!ch[u][w]){
            int v=newnode(len[u]+2);
            fail[v]=ch[getfail(fail[u],right)][w];
            ch[u][w]=v;
            num[v]=num[fail[v]]+1;
        }
        last[right]=ch[u][w];
        if (len[last[right]]==R-L+1) last[!right]=last[right];
        cnt[last[right]]++;
        sum+=num[last[right]];
    }
    void count(){
        for (int i=tot;i>=0;i--) cnt[fail[i]]+=cnt[i];
    }
}G;

int main(){
    int n;
    while(~scanf("%d",&n)){
        G.init();
        for (int i=1;i<=n;i++){
            int tp; scanf("%d",&tp);
            if (tp<=2){
                char s[3];
                scanf("%s",s);
                G.insert(s[0]-'a',tp-1);
            }
            if (tp==3) printf("%d\n",G.tot-1);
            if (tp==4) printf("%lld\n",G.sum);
        }
    }
}

2017 ACM/ICPC 南宁 D Banned Patterns

题目链接:

https://nanti.jisuanke.com/t/19970

 

思路:

KMP做这道题的想法
模板ABCA变成 -1 -1 -1 3
询问CBAABCA变成 -1 -1 -1 1 3 5 3(记作diff)
 
next数组是 0  1  2 3
匹配CBAABCA的时候,我记f[i]为匹配到第i个字符时候,模板串最长能匹配到哪,模板串匹配的最长位置为j
这样f就是  1 2 3 1 2 3 4
 
为什么f[4]变成了1,因为匹配到第四个的时候,要求s[4]==s[4-1],j一直跳到1,这时候diff[4]>=next,意味着这个diff[4]已经没用了,要变成-1
 
这样diff就变成了 -1 -1 -1 -1 3 5 3,此时j指向1
 
然后求f[5],发现5-3>=j,就把3变成-1,来匹配,因为让s[5]==s[2]已经没有用了,模板串长度不够,不需要考虑这个3
 
这样diff又变成了-1 -1 -1 -1 -1 5 3,此时next匹配上了,变成2
 
一直下去,求出所有的f

 

复杂度不会算,因为询问串每次都跳fail,不知道会覆盖多少个不同的状态,最坏情况是可能会是len^2?

 

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

const int N=100005;

int fail[N],cnt[N],deep[N];
map<int,int>ch[N];
int tot,root;
int newnode(){
	tot++;
	ch[tot].clear();
	fail[tot]=0;  cnt[tot]=0;
	return tot;
}
void init(){
	for (int i=0;i<=tot;i++) ch[i].clear();
	root=newnode();
}
void insert(int *s,int n){
	int now=root;
	int _deep=0;
	for (int i=1;i<=n;i++){
		_deep++;
		int w=s[i];
		assert(w<_deep);
		if (!ch[now].count(w)) ch[now][w]=newnode();
		now=ch[now][w];
		deep[now]=_deep;
	}
	cnt[now]++;
}
int getnext(int u,int w){
	if (w>=deep[u]+1) w=-1;
	// for (auto &p:ch[u]){
	// 	printf("ch %d:%d %d\n",u,p.first,p.second);
	// }
	// printf("%d %d %d \n",u,w,ch[u].count(w));
	while(u!=root&&!ch[u].count(w)){
		u=fail[u];
		if (w>=deep[u]+1) w=-1;
	}
	if (ch[u].count(w)) return ch[u][w];
	return u;
}
void build(){
	queue<int>Q;
	fail[root]=root;
	for (auto &p:ch[root]){
		int v=p.second;
		fail[v]=root;
		Q.push(v);
	}
	while(!Q.empty()){
		int u=Q.front(); Q.pop();
		for (auto &p:ch[u]){
			int v=p.second;
			fail[v]=getnext(fail[u],p.first);
			Q.push(v);
		}
	}
}
int T;
int vis[N];
bool query(int *s,int n){
	int now=root;
	for (int i=1;i<=n;i++){
		int w=s[i];
		now=getnext(now,w);
		for (int v=now;v!=root&&vis[v]!=T;v=fail[v]){
			vis[v]=T;
			if (cnt[v]) return 1;
		}
	}
	return 0;
}

char s[N*10];
int a[N*10];
int pre[26];
int main(){
	int t; scanf("%d",&t);
	for (int cas=1;cas<=t;cas++){
		init();
		int n; scanf("%d",&n);
		for (int i=1;i<=n;i++){
			memset(pre,0,sizeof(pre));
			scanf("%s",s+1);
			int m=0;
			for (int j=1;s[j];j++){
				int w=s[j]-'A';
				if (pre[w]) a[j]=j-pre[w];
				else a[j]=-1;
				pre[w]=j;
				m++;
				// printf("%d ",a[j]);
			}
			insert(a,m);
		}
		build();
		// for (int i=1;i<=tot;i++) printf("%d %d %d\n",i,fail[i],deep[i]);
		scanf("%d",&n);
		printf("Case #%d: ",cas);
		for (int i=1;i<=n;i++){
			T++;
			memset(pre,0,sizeof(pre));
			scanf("%s",s+1);
			int m=0;
			for (int j=1;s[j];j++){
				int w=s[j]-'A';
				if (pre[w]) a[j]=j-pre[w];
				else a[j]=-1;
				pre[w]=j;
				m++;
			}
			if (query(a,m)) printf("Y");
			else printf("N");
			if (i==n) printf("\n");
			else printf(" ");
		}

	}
}

 

Educational Codeforces Round 16 F - String Set Queries (CDQ分治+AC自动机)

题目链接:

http://codeforces.com/contest/710/problem/F

 

题意:

1. 集合加入一个串

2. 集合删除一个串

3. 问集合中的串在给定串中出现的次数和(可重复)

要求强制在线

 

思路:

如果是离线的话,直接cdq分治,每次求左半边的AC自动机去更新右半边

强制在线的话,只要保存一下log个左半边的AC自动机就好了

 

复杂度:O((n+s)log(n))

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

const int N=900005;

int ch[N][26],fail[N],cnt[N],f[N];
int tot;
int newnode(){
	tot++;
	for (int i=0;i<26;i++) ch[tot][i]=0;
	fail[tot]=0;  cnt[tot]=0;
	return tot;
}
int merge(int x,int y){
	if (!x||!y) return x+y;
	cnt[x]+=cnt[y];
	for (int i=0;i<26;i++)
		ch[x][i]=merge(ch[x][i],ch[y][i]);
	return x;
}

int root[20];
void insert(int root,char *s,int y){
	int now=root;
	for (int i=0;s[i];i++){
		int w=s[i]-'a';
		if (!ch[now][w]) ch[now][w]=newnode();
		now=ch[now][w];
	}
	cnt[now]+=y;
}
int go(int root,int u,int w){
	while(u!=root&&!ch[u][w]) u=fail[u];
	if (ch[u][w]) return ch[u][w];
	return u;
}
void build(int root){
	queue<int>Q;
	fail[root]=root;
	for (int i=0;i<26;i++){
		int v=ch[root][i];
		if (v){
			fail[v]=root;
			Q.push(v);
		}
	}
	while(!Q.empty()){
		int u=Q.front(); Q.pop();
		f[u]=f[fail[u]]+cnt[u];
		for (int i=0;i<26;i++){
			int v=ch[u][i];
			if (v){
				fail[v]=go(root,fail[u],i);
				Q.push(v);
			}
		}
	}
}
int query(int root,char *s){
	int ret=0;
	int now=root;
	for (int i=0;s[i];i++){
		int w=s[i]-'a';
		now=go(root,now,w);
		ret+=f[now];
	}
	return ret;
}
void init(int &root){
	root=newnode();
	build(root);
}

int tp;
char s[N];
int solve(int l,int r,int deep){
	if (l==r){
		cin>>tp>>s;
		init(root[deep]);
		if (tp<3){
			int y;
			if (tp==1) y=1; else y=-1;
			insert(root[deep],s,y);
		}
		else{
			long long ans=0;
			for (int d=1;d<deep;d++) ans+=query(root[d],s);
			cout<<ans<<endl;
			fflush(stdout);
		}
		return root[deep];
	}
	init(root[deep]);
	int mid=(l+r)/2;
	int rtl=solve(l,mid,deep+1);
	root[deep]=merge(root[deep],rtl);
	build(root[deep]);
	int rtr=solve(mid+1,r,deep+1);
	root[deep]=merge(root[deep],rtr);
	build(root[deep]);
	return root[deep];
}
int main(){
	ios::sync_with_stdio(false);
	int n; cin>>n;
	solve(1,n,1);
}

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;
		}
	}
}

 

 

ACM-ICPC 2018 沈阳赛区网络预赛 E. The cake is a lie(最小圆覆盖至少m个点)

题目链接:

https://nanti.jisuanke.com/t/31446

 

题意:

给你n个半径都为r的圆(n<=300),用一个大圆至少覆盖s个圆,问大圆的最小半径。

 

思路:

首先发现问题转化为求覆盖n个点中至少s个点的最小圆的半径,再加r就是答案。

于是就可以二分这个大圆的半径R,以每个点为圆心画圆,看是否有区域被覆盖了s次。

具体思路看知乎:https://www.zhihu.com/question/266750532/answer/312982493

 

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8,pi=acos(-1);
int sgn(double x){
    if (x<-eps) return -1;
    if (x>eps) return 1;
    return 0;
}
double fm(double x){
    while(x>pi) x-=2*pi;
    while(x<-pi) x+=2*pi;
    return x;
}
struct P{
    double x,y;
    double len(){
        return sqrt(x*x+y*y);
    }
    bool operator<(const P &t)const{
        return x<t.x||x==t.x&&y>t.y;
    }
    bool operator==(const P &t)const{
        return x==t.x&&y==t.y;
    }
    P operator-(const P &t)const{
        return {x-t.x,y-t.y};
    }
    double slope(){
        return atan2(y,x);
    }
};
double dis(const P &a,const P &b){
    return (a-b).len();
}

const int N=305;
P a[N],stk[N*2];
bool check(int n,int S,double R){
    int ans=0;
    for (int i=1;i<=n;i++){
        int cnt=0;
        int top=0;
        for (int j=1;j<=n;j++){
            if (a[i]==a[j]){
                cnt++;
                continue;
            }
            if (sgn(dis(a[i],a[j])-2*R)>=0) continue;
            double alpha=acos(dis(a[i],a[j])/2/R);
            double k=(a[j]-a[i]).slope();
            double st=fm(k-alpha),ed=fm(k+alpha);
            if (st<ed){
                stk[++top]={st,1};
                stk[++top]={ed,-1};
            }
            else{
                cnt++;
                stk[++top]={ed,-1};
                stk[++top]={st,1};
            }
        }
        sort(stk+1,stk+top+1);
        if (cnt>=S) return 1;
        for (int i=1;i<=top;i++){
            if (stk[i].y<0) cnt--;
            else cnt++;
            if (cnt>=S) return 1;
        }
    }
    return 0;
}
double solve(int n,int S){
    double l=0,r=0; 
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) r=max(r,dis(a[i],a[j]));
    while(r-l>1e-6){
        double mid=(l+r)/2;
        if (!check(n,S,mid)) l=mid;
        else r=mid;
    }
    return l;
}
int main(){
    int t; scanf("%d",&t);
    while(t--){
        int n,S; scanf("%d%d",&n,&S);
        for (int i=1;i<=n;i++) scanf("%lf%lf",&a[i].x,&a[i].y);
        double R; scanf("%lf",&R);
        if (S>n) printf("The cake is a lie.\n");
        else printf("%.4f\n",solve(n,S)+R);
    }
}

 

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);
            }
        }
    }
}

hdu4918 (树分治的维护)

题意:

一棵树,每个点有点权w,q次操作

1.将点x的点权修改为y 

2.问离x距离不超过d的点权和

 

思路:

将树分治中的计算部分需要的树状数组提出来,维护这个树状数组,每次查询修改均为O(log2n)

 

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
vector<int>link[N];
struct MSG
{
    int id1,id2;
    int deep;
}msg[N][17];
int w[N];
int c[N*17];
int idn,L[N*2],R[N*2];
int maxfloor[N];
void add(int l,int r,int x,int y)
{
    for (int i=x;i<r-l;i+=(i&-i)) c[l+i]+=y;
}
int get(int l,int r,int x)
{
    if (x<1) return 0;
    if (x>=r-l) x=r-l-1;
    int ret=0;
    for (int i=x;i;i-=(i&-i)) ret+=c[l+i];
    return ret;
}
bool vis[N];
int cent,mxsz;
int size[N];
void findcent(int u,int fa,int n)
{
	size[u]=1;
	int mx=0;
	for (int v:link[u])
	{
		if (vis[v]||v==fa) continue;
		findcent(v,u,n);
		size[u]+=size[v];
		mx=max(mx,size[v]);
	}
	mx=max(mx,n-size[u]);
	if (mx<mxsz) cent=u,mxsz=mx;
}
int getmaxdeep(int u,int fa)
{
    int ret=1;
    for (int v:link[u])
        if (v!=fa&&!vis[v]) ret=max(ret,1+getmaxdeep(v,u));
    return ret;
}
void dfs(int u,int fa,int deep,int idn,int floor,int tp)
{
    if (tp==0) msg[u][floor].id1=idn;
    else msg[u][floor].id2=idn;
    msg[u][floor].deep=deep;
    add(L[idn],R[idn],deep,w[u]);
    for (int v:link[u])
        if (!vis[v]&&v!=fa) dfs(v,u,deep+1,idn,floor,tp);
}
void build(int u,int n,int floor)
{
    cent=u; mxsz=n;
    findcent(u,-1,n);
    u=cent; maxfloor[u]=floor;
    idn++; L[idn]=R[idn-1];
    R[idn]=L[idn]+getmaxdeep(u,-1)+1;

    dfs(u,-1,1,idn,floor,0);

    msg[u][floor].id2=-1;
    for (int v:link[u])
        if (!vis[v])
        {
            idn++; L[idn]=R[idn-1];
            R[idn]=L[idn]+getmaxdeep(v,u)+2;
            dfs(v,u,2,idn,floor,1);
        }

    vis[u]=1;
    for (int v:link[u])
        if (!vis[v])
            if (size[v]<size[u]) build(v,size[v],floor+1);
            else build(v,n-size[u],floor+1);

}
int main()
{
    int n,q;
    while(~scanf("%d%d",&n,&q))
    {
        for (int i=1;i<=n;i++) scanf("%d",&w[i]);
        for (int i=1;i<n;i++)
        {
            int x,y; scanf("%d%d",&x,&y);
            link[x].push_back(y);
            link[y].push_back(x);
        }
        build(1,n,0);
        while(q--)
        {
            char s[2]; int x,y;
            scanf("%s%d%d",s,&x,&y);
            if (s[0]=='?')
            {
                int ans=0;
                for (int floor=0;floor<=maxfloor[x];floor++)
                {
                    int id1=msg[x][floor].id1;
                    int id2=msg[x][floor].id2;
                    int deep=msg[x][floor].deep;
                    ans+=get(L[id1],R[id1],y+2-deep);
                    if (id2!=-1) ans-=get(L[id2],R[id2],y+2-deep);
                }
                printf("%d\n",ans);
            }
            else
            {
                for (int floor=0;floor<=maxfloor[x];floor++)
                {
                    int id1=msg[x][floor].id1;
                    int id2=msg[x][floor].id2;
                    int deep=msg[x][floor].deep;
                    add(L[id1],R[id1],deep,y-w[x]);
                    if (id2!=-1) add(L[id2],R[id2],deep,y-w[x]);
                }
                w[x]=y;
            }
        }
        memset(vis,0,sizeof(vis));
        memset(msg,0,sizeof(msg));
        memset(w,0,sizeof(w));
        memset(c,0,sizeof(c));
        idn=0;
        for (int i=1;i<=n;i++) link[i].clear();
    }
}

NOI2014购票(树的cdq分治+凸壳)

题目链接:

http://uoj.ac/problem/7

 

思路:

如果没有距离限制L的话只要dfs一遍同时维护到根的凸壳就可以(二分位置后可以做到O(1)添加,O(1)恢复)。

这题有个距离限制的话就可以将重心下面的点按d[v]-l[v]排序后,与重心到根的点一起双指针更新。

 

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200005;
struct E
{
	int v; ll z;
};
vector<E>link[N];
ll d[N],p[N],q[N],l[N],f[N];
int fa[N];

struct P
{
	ll d,f;
	ll operator*(const P &b)const
	{
		return d*b.d+f*b.f;
	}
}stk[N];
int top;
long double slope(P a,P b)
{
    long double tmp=1.0;
    return tmp*(b.f-a.f)/(b.d-a.d);
}
void add(P p)
{
	if (top<=1)
	{
		stk[top++]=p;
		return;
	}
	while(top>=2&&slope(p,stk[top-1])>slope(stk[top-1],stk[top-2])) top--;
	stk[top++]=p;
}
void upd(int id)
{
	P vec={-p[id],1};
	int l=0,r=top-1;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (vec*stk[mid]>vec*stk[mid+1]) l=mid+1;
		else r=mid;
	}
	f[id]=min(f[id],p[id]*d[id]+q[id]+vec*stk[l]);
}

int cent,mxsz;
int size[N];
int vis[N];
void findcent(int u,int n)
{
	size[u]=1;
	int mx=0;
	for (E e:link[u])
	{
		int v=e.v;
		if (vis[v]) continue;
		d[v]=d[u]+e.z;
		findcent(v,n);
		size[u]+=size[v];
		mx=max(mx,size[v]);
	}
	mx=max(mx,n-size[u]);
	if (mx<=mxsz) cent=u,mxsz=mx;
}

int nd[N],tot;
void add(int u)
{
	nd[tot++]=u;
	for (E e:link[u])
		if (!vis[e.v]) add(e.v);
}
bool cmp(int i,int j)
{
	return d[i]-l[i]>d[j]-l[j];
}
void solve(int u,int n)
{
	if (n==1) return;
	cent=u;  mxsz=n;
	findcent(u,n);
	int rt=cent;
	for (E e:link[rt]) vis[e.v]++;
	if (u==4&&n==2)
		u=4;
	solve(u,n-size[rt]+1);

	for (E e:link[rt]) vis[e.v]--;
	for (E e:link[rt]) add(e.v);
	sort(nd,nd+tot,cmp);
	int now=rt;
	for (int i=0;i<tot;i++)
	{
		int id=nd[i];
		while(now!=fa[u]&&d[now]>=d[id]-l[id]) add({d[now],f[now]}),now=fa[now];
		if (top) upd(id);
	}
	tot=0; top=0;
	for (E e:link[rt]) solve(e.v,size[e.v]);
}
int main()
{
	int n,t; scanf("%d%d",&n,&t);
	for (int i=2;i<=n;i++)
	{
		f[i]=1ll<<62;
		ll s; scanf("%d%lld",&fa[i],&s);
		link[fa[i]].push_back({i,s});
		scanf("%lld%lld%lld",&p[i],&q[i],&l[i]);
	}
	solve(1,n);
	for (int i=2;i<=n;i++) printf("%lld\n",f[i]);
}

 

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);
		}
	}
}

Codeforces 600E. Lomsat gelral (线段树合并)

为什么线段树合并复杂度是O(nlogn)?因为每次merge都会删除一个节点,一开始节点是nlogn个,所以总复杂度就是O(nlogn)。

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

const int N=100005;
int a[N];

struct NODE
{
	int l,r,ls,rs,mx;
	ll cnt;
}tree[N*18];
int tot;
int root[N];
void init()
{
	tot=0;
}
int newnode(int l,int r)
{
	tot++;
	tree[tot]={l,r,0,0,0,0};
	return tot;
}
void up(int i)
{
	int ls=tree[i].ls,rs=tree[i].rs;
	tree[i].mx=max(tree[ls].mx,tree[rs].mx);
	tree[i].cnt=0;
	if (tree[ls].mx==tree[i].mx) tree[i].cnt+=tree[ls].cnt;
	if (tree[rs].mx==tree[i].mx) tree[i].cnt+=tree[rs].cnt;
}
int build(int l,int r,int x)
{
	int np=newnode(l,r);
	if (l==r)
	{
		tree[np].mx=1;
		tree[np].cnt=x;
		return np;
	}
	int mid=(l+r)/2;
	if (x<=mid) tree[np].ls=build(l,mid,x);
	else tree[np].rs=build(mid+1,r,x);
	up(np);
	return np;
}
int merge(int x,int y,int l,int r)
{
	if (x==0) return y;
	if (y==0) return x;
	if (l==r)
	{
		tree[x].mx+=tree[y].mx;
		return x;
	}
	int mid=(l+r)/2;
	tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid);
	tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
	up(x);
	return x;
}

vector<int>link[N];
ll f[N];
int n;
void dfs(int u,int fa)
{
	root[u]=build(1,n,a[u]);
	for (int i=0;i<link[u].size();i++)
	{
		int v=link[u][i];
		if (v==fa) continue;
		dfs(v,u);
		merge(root[u],root[v],1,n);
	}
	f[u]=tree[root[u]].cnt;
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<n;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		link[x].push_back(y);
		link[y].push_back(x);
	}
	init();
	dfs(1,1);
	for (int i=1;i<=n;i++) printf("%lld ",f[i]);
	for (int i=1;i<=n;i++) link[i].clear();

}
/*

http://codeforces.com/contest/600/problem/E

*/