牛客练习赛 51 (DEF题解)

题目链接

D 羊吃草(二分图最大匹配)

题意:

n n 个羊, m m 个草,每只羊可以吃 [ l i , r i ] [l_i,r_i] 的草,有 Q Q 次询问,每次询问 [ l i , r i ] [l_i,r_i] 区间的草最多可以给多少只羊吃,每次询问独立。 ( n , m < = 400 ) (n,m<=400)

思路:

我们可以从草往羊进行连边,那么每次询问就是求 [ L i , R i ] [L_i,R_i] 区间的草的最大匹配,用匈牙利算法就行。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=500;
vector<int>e[N];
int vis[N];
int match[N];
bool pp(int u,int time){
    for(int v:e[u]){
        if(vis[v]==time)continue;
        vis[v]=time;
        if(match[v]==-1||pp(match[v],time)){
            match[v]=u;
            return 1;
        }
    }
    return 0;
}
int n,m;
int a[N],b[N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    int cnt=0;
    for(int i=1;i<=n;i++){
        for(int j=a[i];j<=b[i];j++)e[j].push_back(i);
    }
    for(int i=1,l,r;i<=m;i++){
        cin>>l>>r;
        for(int j=1;j<=n;j++)match[j]=-1;
        int ans=0;
        for(int j=l;j<=r;j++){
            if(pp(j,++cnt))ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}

E 数列(二分+思维)

题意:

给定 n m n,m 要求构造一个长度为 n n 正整数序列 { a i } \{a_i\} ,使得 a i &lt; = m \sum{a_i}&lt;=m ,并且使得价值最大,价值为 a [ i ] = a [ i 1 ] + 1 a[i]=a[i-1]+1 的出现次数。

思路:

我们可以发现序列最终一个是 1 , 2 , 3....1 , 2 , 3.....1 , 2 , 3... 1,2,3....1,2,3.....1,2,3... 这种形式,那么我们假设我们将数列分成 k k 段,每一段的长度为 l e n i len_i ,那么每一段的和就是 l e n i ( l e n i + 1 ) / 2 len_i*(len_i+1)/2 ,价值为 n k n-k ,我们要使得和尽量小,就是让每一段的长度尽量平均分配,那么对于每一个 k k 我们都可以求出其数字的总和(通过 k , n / k , n % k k,n/k,n\%k ),我们只要求出满足题意的最小的k即可使得价值最大,并且还原序列。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
ll n,m;
vector<ll> v[N];
bool check(ll x){
	int len=n/x,yu=n%x;
	ll temp=x*(len)*(len+1)/2+yu*(len+1);
	return temp<=m;
}
int main()
{
	scanf("%d%d",&n,&m);
	ll l=1,r=n,ans;
	while(l<=r){
		ll mid=(l+r)/2;
		if(check(mid)){
			ans=mid;r=mid-1;
		}
		else l=mid+1;
	}
	ll len=n/ans,yu=n%ans;
	for(ll i=1;i<=ans;i++){
		for(ll j=1;j<=len;j++)v[i].push_back(j);
	}
	for(ll i=1;i<=yu;i++){
		v[i].push_back(len+1);
	}
	for(ll i=1;i<=ans;i++){
		for(ll x:v[i]){
			cout<<x<<" ";
		}
	}
	cout<<endl;
}

F ABCBA(主席树)

题意:

给一棵树,每一个节点是一个字符,每次询问 u , v u,v ,从 u u 沿最短路到 v v 形成一个字符串,问该字符串中有多少个子序列(不用连续)为 a b c b a “abcba”

思路:

参考自橘子猫的题解链接
首先将每一个节点到其叶子节点的区间用主席树来维护,我们要求 a b c b a abcba 的出现次数,由于其是一个回文串,我们可以直接维护 a , b , c , b a , a b , b c , c b , a b c , c b a , b c b , a b c b , b c b a , a b c b a a,b,c,ba,ab,bc,cb,abc,cba,bcb,abcb,bcba,abcba 的数量。
其中 a b c b a abcba =&ls.abcba+rs.abcba+ls.a*rs.bcba+ls.ab*rs.cba+ls.abc*rs.ba+ls.abcb*rs.a&
剩下的也类似。
那么在查询 u , v u,v 的时候我们只要求出 l c a ( u , v ) lca(u,v) ,把两端进行合并即可,要注意当 l c a lca 不是 u , v u,v 时,合并的时候 u l c a u \to lca 的字符串应该时反的。
学习大佬博客中对节点加法运算符的重定义是真的很好用。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3e4+10;
const int mod=10007;
char s[N];
int n,q,u,v;
vector<int>E[N];
//主席树
int ls[N*20],rs[N*20];
struct node{
	int a,b,c,ba,ab,bc,cb,abc,cba,bcb,abcb,bcba,abcba;
}e[N*20];
node operator +(const node &x,const node &y){//要小心检查,错了一个找了1个小时
	node z;
	z.a=(x.a+y.a)%mod;z.b=(x.b+y.b)%mod;z.c=(x.c+y.c)%mod;
	z.ba=(x.ba+y.ba+x.b*y.a)%mod;
	z.ab=(x.ab+y.ab+x.a*y.b)%mod;
	z.bc=(x.bc+y.bc+x.b*y.c)%mod;
	z.cb=(x.cb+y.cb+x.c*y.b)%mod;
	z.abc=(x.abc+y.abc+x.a*y.bc+x.ab*y.c)%mod;
	z.cba=(x.cba+y.cba+x.c*y.ba+x.cb*y.a)%mod;
	z.bcb=(x.bcb+y.bcb+x.b*y.cb+x.bc*y.b)%mod;
	z.abcb=(x.abcb+y.abcb+x.a*y.bcb+x.ab*y.cb+x.abc*y.b)%mod;
	z.bcba=(x.bcba+y.bcba+x.b*y.cba+x.bc*y.ba+x.bcb*y.a)%mod;
	z.abcba=(x.abcba+y.abcba+x.a*y.bcba+x.ab*y.cba+x.abc*y.ba+x.abcb*y.a)%mod;	
	return z;
}
int rt[N*20],cnt=0;
void update(int &root,int pre,int l,int r,int pos,char c){
	root=++cnt;ls[root]=ls[pre];rs[root]=rs[pre];
	if(l==r){
		if(c=='A')e[root].a=1;
		else if(c=='B')e[root].b=1;
		else if(c=='C')e[root].c=1;
		return ;
	}
	int mid=(l+r)/2;
	if(pos<=mid)update(ls[root],ls[pre],l,mid,pos,c);
	else update(rs[root],rs[pre],mid+1,r,pos,c);
	e[root]=e[ls[root]]+e[rs[root]];
}

node query(int root,int l,int r,int LL,int RR){
	if(l>=LL&&r<=RR){
		return e[root];
	}
	int mid=(l+r)/2;
	if(LL>mid)return query(rs[root],mid+1,r,LL,RR);
	else if(RR<=mid)return query(ls[root],l,mid,LL,RR);
	else return query(ls[root],l,mid,LL,RR)+query(rs[root],mid+1,r,LL,RR);//加法很好用
}
//树剖

int deep[N],fa[N],son[N],tot[N];
int dfs1(int u,int f,int dep){
	deep[u]=dep;fa[u]=f;tot[u]=1;
	int pd=-1;
	update(rt[u],rt[f],1,n,deep[u],s[u]);//以深度做为位置
	for(int v:E[u]){
		if(v==f)continue;
		tot[u]+=dfs1(v,u,dep+1);
		if(tot[v]>pd)pd=tot[v],son[u]=v;
	}
	return tot[u];
}
int top[N],idx[N],cnt1=0;
void dfs2(int u,int f){
	idx[u]=++cnt1;top[u]=f;
	if(!son[u])return ;
	dfs2(son[u],f);
	for(int v:E[u]){
		if(!idx[v])dfs2(v,v);
	}
} 
int Lca(int x,int y){
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	if(deep[x]>deep[y])swap(x,y);
	return x;
}

int main()
{
	scanf("%d%d",&n,&q);
	scanf("%s",s+1);
	for(int i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		E[u].push_back(v);E[v].push_back(u);
	}
	dfs1(1,0,1);dfs2(1,0);//
	for(int i=1,x,y;i<=q;i++){
		scanf("%d%d",&x,&y);
		int lca=Lca(x,y);
		if(lca==x){
			node t=query(rt[y],1,n,deep[x],deep[y]);
			printf("%d\n",t.abcba%mod);
		}else if(lca==y){
			node t=query(rt[x],1,n,deep[y],deep[x]);
			printf("%d\n",t.abcba%mod);
		}else{
			node t1=query(rt[x],1,n,deep[lca],deep[x]);
			node t2=query(rt[y],1,n,deep[lca]+1,deep[y]);
			int ans=(t1.abcba+t2.abcba+t1.a*t2.bcba+t1.ba*t2.cba+t1.cba*t2.ba+t1.bcba*t2.a)%mod;//t1为反串
			printf("%d\n",ans);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_40400202/article/details/100608382