USACO19DEC题解

Bronze

A Cow Gymnastics

题目:https://www.luogu.com.cn/problem/P5831
题解:用数组存一下出现位置,O(n^2)枚举一下就好。
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
int n,m,a[100][100],b[100][100],sn,ans;
int main(){
	freopen("gymnastics.in","r",stdin);
	freopen("gymnastics.out","w",stdout);
	read(m);read(n);
	F(i,1,m){
		F(j,1,n){
			read(a[i][j]);
			b[i][a[i][j]]=j;
		}
	}
	F(i,1,n-1){
		F(j,i+1,n){
			sn=1;
			if(b[1][i]>b[1][j]){
				F(k,2,m)
					if(b[k][i]<b[k][j]){
						sn=0;
						break;
					}
			}
			else{
				F(k,2,m){
					if(b[k][i]>b[k][j]){
						sn=0;
						break;
					}
				}
			}
			ans+=sn;
		}
	}
	cout<<ans;
    return 0;
}

B Where Am I?

题目:https://www.luogu.com.cn/problem/P5832
题解:枚举一下K,哈希一下用map一存就行。复杂度:O(n^2)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
const int Mod=1e9+7,bas=131;
set<int>mp;
char c[110];
int n,m,a[110],j,s;
int main(){
	freopen("whereami.in","r",stdin);
	freopen("whereami.out","w",stdout);
	read(n);
	scanf("%s",c+1);
	F(i,1,n){
		a[i]=c[i]-'A'+1;
	}
	F(len,1,n){
		mp.clear();m=1;
		F(i,1,n-len+1){
			j=i+len-1;s=0;
			F(k,i,j){
				s=(ll)s*bas%Mod;
				s+=a[k];
			}	
			if(mp.count(s)){
				m=0;
				break;
			}
			mp.insert(s);
		}
		if(m){
			cout<<len;
			return 0;
		}
	}
    return 0;
}

C Livestock Lineup

题目:https://www.luogu.com.cn/problem/P5833
题解:按字典序爆搜即可。当然,我写的比较麻烦,
建了图,还要用每个点的度数分类讨论,其实复杂度
还是一样,O(8!*8)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
string s[9]={"0","Beatrice","Belinda","Bella","Bessie","Betsy","Blue","Buttercup","Sue"};
char c[100];
int n,m,a[10][10],x,y,du[10],ans[10],v[10];
I D_1(int x){
/*	cout<<x<<" ";
	F(i,1,x){
		cout<<ans[i]<<" ";
	}
	cout<<endl;*/
	if(x==9){
		if(du[ans[x-1]]==2)return;
		F(i,1,8){
			cout<<s[ans[i]]<<endl;
		}
		exit(0);
	}
	re sn=0;
	if(du[ans[x-1]]>=1){
		F(i,1,8){
			if(a[ans[x-1]][i]&&!v[i]){
				sn++;
			}
		}
		if(sn==2)return;
		sn=1;
		F(i,1,8){
			if(a[ans[x-1]][i]&&!v[i]){
				ans[x]=i,v[i]=1,D_1(x+1),v[i]=0;sn=0;	
				break;
			}
		}
		if(!sn)return;
	}
	F(i,1,8){
		if(v[i])continue;
		ans[x]=i,v[i]=1,D_1(x+1);
		ans[x]=0;v[i]=0;
	}
}
int main(){
	freopen("lineup.in","r",stdin);
	freopen("lineup.out","w",stdout);
	read(n);
	memset(a,0,sizeof(a));
	memset(du,0,sizeof(du));
	F(i,1,n){
		cin>>c+1;
		m=strlen(c+1);
		if(m==3)x=8;
		if(m==4)x=6;
		if(m==6)x=4;
		if(m==7)x=2;
		if(m==8)x=1;
		if(m==9)x=7;
		if(m==5){
			if(c[3]=='l')x=3;
			else x=5;
		}
		cin>>c+1>>c+1>>c+1>>c+1>>c+1;
		m=strlen(c+1);
		if(m==3)y=8;
		if(m==4)y=6;
		if(m==6)y=4;
		if(m==7)y=2;
		if(m==8)y=1;
		if(m==9)y=7;
		if(m==5){
			if(c[3]=='l')y=3;
			else y=5;
		}
		du[x]++;du[y]++;
		a[x][y]=1;a[y][x]=1;
		//cout<<x<<" "<<y<<endl;
	}
	F(i,1,n){
		if(du[i]<2){
			ans[1]=i;v[i]=1;
			D_1(2);v[i]=0;
		}
	}
	//cout<<"!";
    return 0;
}

Silver

A MooBuzz

题目:https://www.luogu.com.cn/problem/P5834
题解:可以明显看出,每15次,有8个数被报出来。
随便写写就过了
复杂度O(1)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
int n,m,ans;
int main(){
	freopen("moobuzz.in","r",stdin);
	freopen("moobuzz.out","w",stdout);
	read(n);
	if(n%8==0){
		ans=(n/8)*15;
		ans--;
		cout<<ans;
		return 0;
	}
	ans=(n/8)*15;
	n%=8;m=0;
	F(i,1,n){
		while(m%3==0||m%5==0)m++;
		if(i==n){
			ans+=m;
			break;
		}
		m++;
	}
	cout<<ans;
    return 0;
}

B Meetings

题目:https://www.luogu.com.cn/problem/P5835
题解:相信这道题的两个套路大家应该都见过:
1.不管这些牛怎么走,他们的相对位置都是不变的(没见过的一定要细细斟酌)
2.两头牛相遇后,可以看做是“擦肩而过”,也就是并没有改变方向。所以,
我们可以直接算出每头牛走到终点的时间。
知道了这两个性质,做法就顺水推舟了:
1.用类似归并排序的方法算出时刻T,由性质1,我们对时间和重量做出贡献的并不是一头牛(一定要小心);
2.因为时间已经确定,所以可以固定向一个方向走的牛不动,相当于另一个方向走的牛走了2*T个单位距离。
对两种方向的牛分别排序,双指针扫一下即可。具体实现看代码。
复杂度:O(nlogn)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register ll
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline ll
typedef long long ll;
I read(ll &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
struct P{
	ll x,w;
}l[50500],r[50500],p[101000];
ll n,m,num,tot,sum,W,X,d,Y,cnt,T,ans,L,R;
inline bool bb1(P a,P b){
	return a.x==b.x?a.w>b.w:a.x>b.x;
}
inline bool bb2(P a,P b){
	return a.x==b.x?a.w<b.w:a.x<b.x;
}
IN max(ll x,ll y){
	return x>y?x:y;
}
int main(){
	freopen("meetings.in","r",stdin);
	freopen("meetings.out","w",stdout);
	read(n);read(m);
	tot=sum=num=0;
	F(i,1,n){
		read(W);read(X);read(d);num+=W;
		p[i].x=X;p[i].w=W;
		if(d==1)tot++,l[tot].x=X,l[tot].w=W;
		else sum++,r[sum].x=X,r[sum].w=W;
	}
	sort(l+1,l+1+tot,bb1);
	sort(r+1,r+1+sum,bb2);
	sort(p+1,p+1+n,bb2);
	X=1;Y=1;cnt=0;T=0;L=1;R=n;
	while(cnt*2<num){
		if(m-l[X].x>r[Y].x)cnt+=p[L++].w,T=r[Y].x,Y++;
		else cnt+=p[R--].w,T=m-l[X].x,X++;
	}
	//cout<<X<<" "<<Y<<" "<<T<<endl;
	sort(l+1,l+1+tot,bb2);
	X=1;Y=1;
	F(i,1,sum){
		while(X<=tot&&l[X].x<max(0ll,r[i].x-T-T))X++;
		while(Y<=tot&&l[Y].x<=r[i].x)Y++;
		//cout<<X<<" "<<Y<<endl;
		ans+=(Y-X);
	}
	cout<<ans;
    return 0;
}

C Milk Visits

题目:https://www.luogu.com.cn/problem/P5836
题解:树剖+线段树板子题。详细讲解请见下一道Milk Visits。
复杂度:O(nlog^2n)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
char c[101000];
struct E{
	int to,nt;
}e[202000];
#define T e[k].to
char sit;
int n,m,head[101000],fa[101000],X,Y,tot=-1,tr[101000],t[101000],siz[101000],top[101000],son[101000],id[101000],dep[101000];
I D_1(int x,int fat,int depth){
	siz[x]=1;dep[x]=depth;son[x]=-1;fa[x]=fat;
	re maxi=-1;
	for(re k=head[x];k!=-1;k=e[k].nt){
		if(T==fat)continue;
		D_1(T,x,depth+1);
		siz[x]+=siz[T];
		if(maxi<siz[T])maxi=siz[T],son[x]=T;
	}
}
IN lbt(int x){
	return x&(-x);
}
I modi(int x){
	while(x<=n)tr[x]++,x+=lbt(x);
}
I modify(int x){
	while(x<=n)t[x]++,x+=lbt(x);
}
I D_2(int x,int fat,int topi){
	top[x]=topi;id[x]=++tot;
	if(c[x]=='H')modi(id[x]);else modify(id[x]);
	if(son[x]!=-1)D_2(son[x],x,topi);
	for(re k=head[x];k!=-1;k=e[k].nt){
		if(T==fat||T==son[x])continue;
		D_2(T,x,T);
	}
}
IN ques_sum(int x){
	re res=0;
	while(x)res+=tr[x],x-=lbt(x);
	return res;
}
IN query(int x){
	re res=0;
	while(x)res+=t[x],x-=lbt(x);
	return res;
}
IN ques(int x,int y,char S){
	re res=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		if(S=='H')res=(ques_sum(id[x])-ques_sum(id[top[x]]-1));
		else res=(query(id[x])-query(id[top[x]]-1));
		if(res)return 1;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	if(S=='H')res=ques_sum(id[y])-ques_sum(id[x]-1);
	else res=query(id[y])-query(id[x]-1);
	if(res)return 1;
	return 0;
}
int main(){
	freopen("milkvisits.in","r",stdin);
	freopen("milkvisits.out","w",stdout);
	read(n);read(m);
	cin>>c+1;
	memset(head,-1,sizeof(head));
	tot=-1;
	F(i,1,n-1){
		read(X);read(Y);
		e[++tot].to=Y;
		e[tot].nt=head[X];
		head[X]=tot;
		e[++tot].to=X;
		e[tot].nt=head[Y];
		head[Y]=tot;
	}
	D_1(1,0,1);
	tot=0;
	D_2(1,0,1);
	while(m--){
		read(X);read(Y);cin>>sit;
		cout<<ques(X,Y,sit);
	}
    return 0;
}

Gold

A Milk Pumping

题目:https://www.luogu.com.cn/problem/P5837
题解:从1到1000枚举这个最小流量,跑最短路就好。
复杂度:O(1000*(n+m)logn)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
struct E{
	int to,nt,w,v;
}e[2020];
#define T e[k].to
const int INF=1e9+7;
int n,m,head[1010],X,Y,W,V,dis[1010],vis[1010],p,ans,tot;
queue<int>q;
int main(){
	freopen("pump.in","r",stdin);
	freopen("pump.out","w",stdout);
	read(n);read(m);
	memset(head,-1,sizeof(head));tot=-1;
	F(i,1,m){
		read(X);read(Y);read(W);read(V);
		e[++tot].to=Y;
		e[tot].nt=head[X];
		head[X]=tot;
		e[tot].w=W;
		e[tot].v=V;
		e[++tot].to=X;
		e[tot].nt=head[Y];
		head[Y]=tot;
		e[tot].w=W;
		e[tot].v=V;
	}
	ans=0;
	F(val,1,1000){
		F(i,1,n)dis[i]=INF,vis[i]=0;
		dis[1]=0,vis[1]=1;
		q.push(1);
		while(!q.empty()){
			p=q.front();q.pop();vis[p]=0;
			for(re k=head[p];k!=-1;k=e[k].nt){
				if(e[k].v<val)continue;
				if(dis[p]+e[k].w<dis[T]){
					dis[T]=dis[p]+e[k].w;
					if(!vis[T]){
						vis[T]=1;
						q.push(T);
					}
				}
			}
		}
		if(dis[n]!=INF)ans=max(ans,val*1000000/dis[n]);
	}
	cout<<ans;
    return 0;
}

B Milk Visits

题目:https://www.luogu.com.cn/problem/P5838
题解:在上一道Milk Visits中,我们用两个线段树维护了树上的信息,
这里,我们显然不能直接开1e5棵线段树,所以考虑避免这个问题。
Approach 1:可持久化线段树
这个嘛。。。不会的打一下模板题就会了,这里就不讲了。
Approach 2:离线
遇到不强制在线,且无修改的题目,离线算法是一定要take into consideration的。
我们将每个点的点权当做修改,和查询中每个人的喜好牛奶一起排序,按点权排序。
这样,我们就只需要在一棵线段树上进行修改、查询操作了。每次先打个lazytag清空
之前的信息,然后将一个点权的所有点插入线段树,然后查询。
具体实现还是看代码。
复杂度:O(nlog^2n)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
char c[101000];
struct E{
	int to,nt;
}e[202000];
struct Q{
	int x,y,p,id;
	friend bool operator < (Q a,Q b){
		return a.p==b.p?a.id<b.id:a.p<b.p;
	}
}q[202000];
#define T e[k].to
char sit;
int n,m,now,ans[101000],a[100100],tr[404000],laz[404000],head[101000],fa[101000],X,Y,tot=-1,siz[101000],top[101000],son[101000],id[101000],dep[101000];
I D_1(int x,int fat,int depth){
	siz[x]=1;dep[x]=depth;son[x]=-1;fa[x]=fat;
	re maxi=-1;
	for(re k=head[x];k!=-1;k=e[k].nt){
		if(T==fat)continue;
		D_1(T,x,depth+1);
		siz[x]+=siz[T];
		if(maxi<siz[T])maxi=siz[T],son[x]=T;
	}
}
I D_2(int x,int fat,int topi){
	top[x]=topi;id[x]=++tot;
	q[tot].x=x;q[tot].y=id[x];q[tot].p=a[x];q[tot].id=-1;
	//将每个点看做修改,id为负是为了使其排序时排在查询前面 
	if(son[x]!=-1)D_2(son[x],x,topi);
	for(re k=head[x];k!=-1;k=e[k].nt){
		if(T==fat||T==son[x])continue;
		D_2(T,x,T);
	}
}
#define all 1,1,n
#define lt k<<1,l,mid
#define rt k<<1|1,mid+1,r
I modi(int k,int l,int r,int x){
	if(l==r){
		tr[k]=1;
		return;
	}
	if(laz[k])tr[k<<1]=tr[k<<1|1]=laz[k]=0,laz[k<<1]=laz[k<<1|1]=1;
	re mid=(l+r)>>1;
	if(x<=mid)modi(lt,x);
	else modi(rt,x);
	tr[k]=tr[k<<1]|tr[k<<1|1];
}
IN ques_sum(int k,int l,int r,int x,int y){
	if(x>r||y<l)return 0;
	if(x<=l&&r<=y)return tr[k];
	if(laz[k])tr[k<<1]=tr[k<<1|1]=laz[k]=0,laz[k<<1]=laz[k<<1|1]=1;
	re mid=(l+r)>>1;
	return ques_sum(lt,x,y)|ques_sum(rt,x,y);
}
IN ques(int x,int y){
	re res=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		res|=ques_sum(all,id[top[x]],id[x]);
		if(res)return 1;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	res|=ques_sum(all,id[x],id[y]);
	if(res)return 1;
	return 0;
}
int main(){
	freopen("milkvisits.in","r",stdin);
	freopen("milkvisits.out","w",stdout);
	read(n);read(m);
	F(i,1,n){
		read(a[i]);
	}
	memset(head,-1,sizeof(head));
	tot=-1;
	F(i,1,n-1){
		read(X);read(Y);
		e[++tot].to=Y;
		e[tot].nt=head[X];
		head[X]=tot;
		e[++tot].to=X;
		e[tot].nt=head[Y];
		head[Y]=tot;
	}
	D_1(1,0,1);
	tot=0;
	D_2(1,0,1);
	F(i,1,m){
		tot++;
		read(q[tot].x);read(q[tot].y);read(q[tot].p);q[tot].id=i;
	}
	sort(q+1,q+1+tot);
	now=q[1].p;
	F(i,1,tot){
		if(q[i].p>now)laz[1]=1,tr[1]=0,now=q[i].p;//点权变化时,清空线段树,打懒标记是一个不错的处理方式 
		if(q[i].id<0)modi(all,q[i].y);
		else ans[q[i].id]=ques(q[i].x,q[i].y);
	}
	F(i,1,m){
		cout<<ans[i];
	}
    return 0;
}

C Moortal Cowmbat

题目:https://www.luogu.com.cn/problem/P5839
题解:首先观察到m很小,先O(m^3)floyd判一下两两之间的最小代价。
设f[i][j]表示处理了前i个字符,最后一堆字符为j的最小代价。
可以口胡一下转移方程
暴力转移是O(n^2m^2)的,我们无法接受。
考虑优化这个dp。

O(n^2m)

考虑到当前的转移并不需要知道上一次的最后一段的字母是什么,我们
可以先对i的所有的f[i][j]取个min,再进行转移。

O(nm)

发现对于当前的i,可以转移到它的p是从1开始的一段连续区间。
我们设f[i]表示处理完前i个字母的最小代价,sum[i][j]表示从1到i,
将所有字母都变为j的代价。
这样,转移方程就可以写为:
f[i]=min{f[p]+sum[i][j]-sum[p][j},p (in) [1,i-k]
枚举特定的j时,可以将sum[i][j]从这个max里提出来。
发现只需要取f[p]-sum[p][j]的min。
我们设mx[j]表示对于p (in) [1,i-k],f[p]-sum[p][j]的最小值。
这样,转移就变成了O(1)。具体实现还有一些小细节,看代码吧~
复杂度:O(m^3+nm)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
#define I inline void
#define IN inline int
typedef long long ll;
I read(int &res){
    re g=1;register char ch=getchar();res=0;
    while(!isdigit(ch)){
        if(ch=='-')g=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        res=(res<<3)+(res<<1)+(ch^48);
        ch=getchar();
    }
    res*=g;
}
const int INF=1e9+7;
char c[101000];
int n,m,k,a[101000],dis[30][30],mx[30],sum[101000][30],f[101000];
int main(){
	freopen("cowmbat.in","r",stdin);
	freopen("cowmbat.out","w",stdout);
	read(n);read(m);read(k);
	cin>>c+1;
	F(i,1,n){
		a[i]=c[i]-'a'+1;
	}
	F(i,1,m){
		F(j,1,m){
			read(dis[i][j]);
		}
	}
	F(k,1,m){
		F(i,1,m){
			F(j,1,m){
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//Floyd
			}
		}
	}
	memset(mx,0,sizeof(mx));
	memset(sum,0,sizeof(sum));
	F(i,1,k-1){
		F(j,1,m){
			sum[i][j]=sum[i-1][j]+dis[a[i]][j];//预处理sum 
		}
	}
	F(i,k,n){
		f[i]=INF;
		if(i>=(k<<1)){
			F(j,1,m){
				mx[j]=min(mx[j],f[i-k]-sum[i-k][j]);//i<2k时不存在合法的f 
			}
		}
		F(j,1,m){
			sum[i][j]=sum[i-1][j]+dis[a[i]][j];
			f[i]=min(f[i],mx[j]+sum[i][j]);
		}
	}
	cout<<f[n];
    return 0;
}

Platinum

A Greedy Pie Eaters

题目:http://www.usaco.org/index.php?page=viewproblem2&cpid=972
题目大意:有n个派,m头牛,每头牛有一个区间[l,r]和一个权值w。
没有两头牛有相同的[l,r]。
你需要选择一些牛,按一定顺序吃派。每头牛会吃完他的那个区间的所有派。
问在满足每头牛都至少有一个派吃的情况下,能获得的最大权值是多少。
n<=300,m<=45000
题解:n很小,可以想到区间DP。
设f[i][j]表示吃掉的派是[i,j]的子集的最大权值。
显然,f[i][j]=max{f[i][k-1]+f[k][j]},k(in)[i,j]
但这样的转移不能加入新的牛。
可以考虑待合并的两个状态f[i][k-1]和f[k+1][j],此时k这个派还没有牛动。
我们设mx[k][i][j]表示满足i<=l[k]<=k<=r[k]<=j的所有牛的最大权值。
那么加入新牛的转移方程就出炉了:
f[i][j]=max{f[i][k-1]+mx[k][i][j]+f[k+1][j]},k(in)(i,j)
复杂度O(n^3)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline int
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
int n,m,mx[330][330][330],f[330][330],W,X,Y;
int main(){
	freopen("pieaters.in","r",stdin);
	freopen("pieaters.out","w",stdout);
	read(n);read(m);
	memset(mx,0,sizeof(mx));
	F(i,1,m){
		read(W);read(X);read(Y);
		F(j,X,Y)mx[j][X][Y]=max(W,mx[j][X][Y]);
	}
	F(i,1,n){
		FOR(j,i,1){
			F(k,i,n){
				if(j>1)mx[i][j-1][k]=max(mx[i][j-1][k],mx[i][j][k]);
				if(k<n)mx[i][j][k+1]=max(mx[i][j][k+1],mx[i][j][k]);
			}
		}
	}
	FOR(i,n,1){
		F(j,i,n){
			F(k,i,j-1)f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
			F(k,i,j)f[i][j]=max(f[i][j],mx[k][i][j]+(k>i?f[i][k-1]:0)+(k<j?f[k+1][j]:0));
		}
	}
	printf("%d",f[1][n]);
	return 0;
}

B Bessie's Snow Cow

题目:http://www.usaco.org/index.php?page=viewproblem2&cpid=973
题目大意:给你一棵树,要求支持两种操作:
1.给定x,c,给x的子树染上颜色c(一个节点可以有多个颜色)
2.给定x,求x的子树的所有节点的所有颜色个数和(每个节点单独算)
题解:这个题有两个难点:
1.修改时,如何更新其对子树内节点的贡献,以及如何上传对祖先的影响;
2.查询时,如何一次性统计子树内所有节点的贡献
我们发现,如果将一个操作的复杂度变为O(logn)甚至O(1),另一个操作的
复杂度将不可避免地到达O(n)。
考虑统计答案。答案可以分为两个部分:一是x或x的祖先已经染过的颜色,
这些的贡献可以一并计算;二是子树内染色的情况。
对于第一个,我们在每次修改时区间加就行了,当然还要撤销之前子树内
的一些修改操作的贡献。
我们以这棵树的dfs序为下标开一个树状数组,用差分维护每个节点已经染
上的颜色个数,再开1e5个set记录下每个颜色的染色情况,避免重复染色。
每次修改操作步骤如下:
1.判断x的祖先是否已经被染上c色。若已经染色,直接退出;
2.查询x子树内是否有节点染上c色。若已经染色,撤销其影响并在set中
删除此节点;
3.将此次修改加入set,进行区间修改。
那对于第二个,又该怎么求呢?
因为这些贡献是一段dfs序连续区间的单点贡献,我们可以另开一个树状数组
记录这个贡献。每次修改只需要单点+siz[x]即可。
修改操作的步骤同上。每次只需要查询[id[x]+1,id[x]+siz[x]-1]这段区间的
贡献即可(也就是x子树除去x节点的贡献)
设两个查询得到的值是ans1和ans2,那么答案即为:siz[x]*ans1+ans2
复杂度:因为每次修改只会被加入和撤销一次,每次都是O(logn),
查询也是O(logn)的,所以总复杂度即为O(nlogn)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register ll
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline ll
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
const ll INF=1e9+7;
struct E{
	ll to,nt;
}e[202000];
#define T e[k].to
ll n,m,tot,sit,X,Y,head[101000],siz[101000],id[101000],p[101000],out[101000];
struct BIT{
	ll tr[101000],res;
	IN lbt(ll x){
		return x&(-x);
	}
	I modi(ll x,ll w){while(x<=n){tr[x]+=w,x+=lbt(x);}}
	IN ques(ll x){res=0;while(x){res+=tr[x],x-=lbt(x);}return res;}
}A,B;
set<ll>s[101000];
I modify(ll x,ll sn){
	A.modi(id[x],sn);
	A.modi(out[x]+1,-1*sn);
	B.modi(id[x],sn*siz[x]);
	//cout<<id[x]<<" "<<out[x]<<" "<<sn<<endl;
}
I D_1(ll x,ll fa){
	id[x]=++tot;siz[x]=1;p[tot]=x;
	for(re k=head[x];k!=-1;k=e[k].nt){
		if(T==fa)continue;
		D_1(T,x);siz[x]+=siz[T];
	}
	out[x]=tot;
}
int main(){
	freopen("snowcow.in","r",stdin);
	freopen("snowcow.out","w",stdout);
	read(n);read(m);
	memset(head,-1,sizeof(head));
	tot=-1;
	F(i,1,n-1){
		read(X);read(Y);
		e[++tot].to=Y;
		e[tot].nt=head[X];
		head[X]=tot;
		e[++tot].to=X;
		e[tot].nt=head[Y];
		head[Y]=tot;
	}
	F(i,0,100000)s[i].insert(INF);
	tot=0;
	D_1(1,0);
	//F(i,1,n)cout<<id[i]<<" "<<siz[i]<<" "<<out[i]<<endl;
	while(m--){
		read(sit);read(X);
		if(sit==1){
			read(Y);
			auto P=s[Y].upper_bound(id[X]);
			if(P!=s[Y].begin()&&out[p[*prev(P)]]>=out[X])continue;
			while(1){
				P=s[Y].upper_bound(id[X]);
				if(*P>out[X])break;
				modify(p[*P],-1);
				s[Y].erase(P);
			}
			s[Y].insert(id[X]);
			modify(X,1);
		}
		else printf("%lld
",siz[X]*A.ques(id[X])+B.ques(out[X])-B.ques(id[X]));//printf("%d %d %d %d %d %lld
",id[X],out[X],A.ques(id[X]),B.ques(out[X]),B.ques(id[X]),(ll)siz[X]*A.ques(id[X])+(ll)B.ques(out[X])-(ll)B.ques(id[X]));
	}
	return 0;
}

C Tree Depth

待填坑~

原文地址:https://www.cnblogs.com/Purple-wzy/p/12073907.html