2021暑假牛客多校4

C.LCS

题意:
构造三个长度为n的字符串 s 1 , s 2 , s 3 s_1,s_2,s_3 s1,s2,s3,使1,2的最长公共字串长度为a,23长度为b,13长度为c。

主要思路:
可以找到a,b,c中最段短的长度为z,先给每个字符串构造前缀为z个‘a’,然后问题就转化为了:a-z,b-z,c-z
让1,2加上a-z个’b’,2,3加上b-z个‘c’,1,3加上c-z个’d’。再分别把缺的部分补上不同的字符。
注意,当n<x+y-z(b的长度)时无法构造。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
    int a,b,c,n;
    cin>>a>>b>>c>>n;
    int z=min(a,min(b,c)),x=max(a,max(b,c)),y=a+b+c-x-z;
    if(n<x+y-z) cout<<"NO"<<endl;

    else
    {
        string s1="",s2="",s3="";
        for(int i=0;i<z;i++)
        {
            s1+='a',s2+='a',s3+='a';
        }
        for(int i=0;i<a-z;i++)
        {
            s1+='b',s2+='b';
        }
        for(int i=0;i<b-z;i++)
        {
            s2+='c',s3+='c';
        }
        for(int i=0;i<c-z;i++)
            s1+='g',s3+='g';
        while(s1.size()<n)
            s1+='d';
        while(s2.size()<n)
            s2+='e';
        while(s3.size()<n)
            s3+='f';
        cout<<s1<<endl<<s2<<endl<<s3<<endl;
    }
    return 0;
}

E.Tree Xor

题意:
给定一棵树,知道两个点的权值的抑或和,不知道每个点的权值,但是给了每个点的权值范围。问可以有多少组合法权值?

主要思路:
首先,做这个题的基础就是知道点的权值只要确定了一个点,其他的点都会跟着确定下来。
然后再深入分析,可以让1结点的权值为0,并且让1号结点做树根,dfs遍历,确定下来一组初始权值。
之后利用抑或的性质:对1号结点 ⊕ a \oplus a a,则整个数的每一个结点的权值都要 ⊕ a \oplus a a.
然后开始想的暴力算法就是遍历每个结点,遍历a的值,若是 w [ i ] ⊕ a w[i]\oplus a w[i]a 在限制范围内,就 n u m [ a ] + = 1 num[a]+=1 num[a]+=1,最后统计 n u m [ i ] = = n num[i]==n num[i]==n的数量,即为最终答案。
但是时间复杂度明显太高了,显然会超时,所以应该思考优化方法:
①可以利用dfs+贪心+差分 的思想,在遍历树,求初始权值遍历树的时候同时求出来每个点的初始权值可以抑或的那个a的范围,为了减小时间复杂度,只需要利用差分处理小区间的端点即可。
②也可以利用tire树(其实根本没什么用,只需要二进制从大到小递归即可,有点类似于字典树,但是没有必要构建),或线段树来进行区间操作求解。

//递归写法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef pair<int,int> pa;
const int N=1e5+10;
pa l[N];
int n;
vector<pa> a[N];
int w[N];
vector<pa> range;
void dfs2(int n,int val,int c)//把^val<=n的数+c ,找到区间两个端点进行差分预处理 
{
	if(n==0)//只有val本身^val才能<=0 
	{
		range.push_back({val,c});
		range.push_back({val+1,-c});
	} 
	int cur=0;
	for(int i=29;i>=0;i--)
	{
		int x=(n&(1<<i));
		int y=(val&(1<<i));
		if(x)//n的这一位=1,求x^val<=(1<<i)  x的取值范围 
		{
			int temp=0;
			if(y) temp=1<<i;
			range.push_back({cur+temp,c});
			range.push_back({cur+temp+(1<<i),-c});
		}
		cur+=x^y;
		if(!i)
		{
			range.push_back({cur,c});//特殊情况  x^val=n
			range.push_back({cur+1,-c});
		}
	} 
}
void dfs(int x,int fa,int val)
{
	if(l[x].first) dfs2(l[x].first-1,val,-1);
	dfs2(l[x].second,val,1);
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i].first==fa) continue;
		dfs(a[x][i].first,x,val^a[x][i].second);
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>l[i].first>>l[i].second;
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		a[x].push_back({y,z});
		a[y].push_back({x,z});
	}
	dfs(1,0,0);
	sort(range.begin(),range.end());
	int sum=0;
	int ans=0;
	for(int i=0;i<range.size();i++)
	{
		sum+=range[i].second;
		if(sum==n)
			ans+=range[i+1].first-range[i].first;
	}
	cout<<ans<<endl;
} 
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
const int N=100010;
int h[N],e[2*N],ne[2*N],w[2*N],idx;
void add(int a,int b,int c){e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;}
int L[N],R[N];
int val[N];
struct node
{
    int l,r;
}tree[N*40];
int rt,cnt,n;
vector<pair<int,int>> seg[2];
void query(int k,int a,int b)// x^a<=b
{
//     int u=rt;
    int cur=0;
    for(int i=29;i>=0;i--)//30位
    {
        int ai=a>>i&1;
        int bi=b>>i&1;
        if(bi==1) //b=1
        {
            if(ai==0) 
            {
                seg[k].push_back({cur,cur+(1<<i)-1});
                cur+=1<<i;
//                 if(!tree[u].r) tree[u].r=++cnt;
//                 u=tree[u].r;
            }
            else
            {
                seg[k].push_back({cur+(1<<i),cur+(1<<i)+(1<<i)-1});
                
//                 if(!tree[u].l) tree[u].l=++cnt;
//                 u=tree[u].l;
            }
        }
        else
        {
            if(ai==0)
            {
//                 if(!tree[u].l) tree[u].l=++cnt;
//                 u=tree[u].l;
            }
            else 
            {
                cur+=1<<i;
//                 if(!tree[u].r) tree[u].r=++cnt;
//                 u=tree[u].r;
            }
        }
    }
    seg[k].push_back({cur,cur});
}
void dfs(int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int v=e[i];
        if(v==fa) continue;
        val[v]=val[u]^w[i];
        dfs(v,u);
    }
}
int solve()
{
    vector<pair<int,int>>vec;
    for(auto t:seg[0]) 
    {
        vec.push_back({t.first,-1});
        vec.push_back({t.second+1,+1});
    }
    for(auto t:seg[1]) 
    {
        vec.push_back({t.first,+1});
        vec.push_back({t.second+1,-1});
    }
    sort(vec.begin(),vec.end());
    
    vec.push_back({1<<30,0});
    int ans=0;
    int cur=0;
    for(int i=0;i<vec.size()-1;i++)
    {
        cur+=vec[i].second;
        if(cur==n) ans+=vec[i+1].first-vec[i].first;
    }
    return ans;
}
int main()
{
    n=rd();
    for(int i=1;i<=n;i++) L[i]=rd(),R[i]=rd();
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++)
    {
        int u=rd(),v=rd(),w=rd();
        add(u,v,w),add(v,u,w);
    }
    
    dfs(1,0);
    for(int i=1;i<=n;i++) 
    {
        if(L[i]>0) query(0,val[i],L[i]-1);
        query(1,val[i],R[i]);
    }
    printf("%d\n",solve());
}

I.Inverse Pair

题意:
输入一个1~n的全排列,可以给每个数加一或者是不加,问加完以后的逆序对数。

主要思路:
可以用树状数组求逆序对数,也可以用归并排序求。
主要难点是进行加一操作后逆序对数减少多少如何计算。
可以开一个数组,从数值1遍历到n(开始是下标1~n)
如果i的下标比i-1的下标小,并且i-1没有加一,逆序对数-1

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10;
int c[N],p[N],a[N];
int n;
int lowbit(int x)
{
	return x&-x;
}
void add(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
		c[i]+=k;
}
long long sum(int x)
{
	long long ans=0;
	while(x)
	{
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}
int main()
{
    cin>>n;
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        p[a[i]]=i;
        ans=ans+i-sum(a[i])-1;
        add(a[i],1);
    }
    bool flag=0;
    for(int i=2;i<=n;i++)
    {
        if(flag||p[i]>p[i-1]) flag=0;
        else
        {
            ans--;
            flag=1;
        }
    }
    cout<<ans<<endl;
    return 0;
}

J.Average

题意:
给定长为n的数组a和长为m的数组b,w[i,j]为 a i + b j a_i+b_j ai+bj.
求子区间的最大平均值?(给定了最小长x,和最小宽y).

主要思路:
首先推导一下公式:
∑ i = l 1 r 1 ∑ j = l 2 r 2 w [ i , j ] / ( r 1 − l 1 + 1 ) ( r 2 − l 2 + 1 ) \sum_{i=l_1}^{r_1} \sum_{j=l_2}^{r_2}w[i,j]/(r_1-l_1+1)(r_2-l_2+1) i=l1r1j=l2r2w[i,j]/(r1l1+1)(r2l2+1)
∑ i = l 1 r 1 ∑ j = l 2 r 2 w [ i , j ] = ∑ i = l 1 r 1 a [ i ] ∗ ( r 2 − l 2 + 1 ) + ∑ j = l 2 r 2 b [ j ] ( r 1 − l 1 + 1 ) \sum_{i=l_1}^{r_1} \sum_{j=l_2}^{r_2}w[i,j]=\sum_{i=l_1}^{r_1}a[i]*(r_2-l_2+1)+\sum_{j=l_2}^{r_2}b[j](r_1-l_1+1) i=l1r1j=l2r2w[i,j]=i=l1r1a[i](r2l2+1)+j=l2r2b[j](r1l1+1)
∑ i = l 1 r 1 ∑ j = l 2 r 2 w [ i , j ] / ( r 1 − l 1 + 1 ) ( r 2 − l 2 + 1 ) = ∑ i = l 1 r 1 a [ i ] / ( r 1 − l 1 + 1 ) + ∑ j = l 2 r 2 b [ j ] / ( r 2 − l 2 + 1 ) \sum_{i=l_1}^{r_1} \sum_{j=l_2}^{r_2}w[i,j]/(r_1-l_1+1)(r_2-l_2+1)=\sum_{i=l_1}^{r_1}a[i]/(r_1-l_1+1)+\sum_{j=l_2}^{r_2}b[j]/(r_2-l_2+1) i=l1r1j=l2r2w[i,j]/(r1l1+1)(r2l2+1)=i=l1r1a[i]/(r1l1+1)+j=l2r2b[j]/(r2l2+1)

你会发现,+前后是相互独立的分别讨论就行。
单纯暴力的话肯定会超时的,这里采用二分优化。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
bool check(int a[],double c,int x,int n)
{
    double s[N]={0};
    for(int i=1;i<=n;i++)
        s[i]=s[i-1]+a[i]-c;
    double mi=0;
    double ans=-1e9;
    for(int i=x;i<=n;i++)
    {
        mi=min(mi,s[i-x]);
        ans=max(ans,s[i]-mi);
    }
    return ans<=0;
}
double find(int a[],int x,int n)
{
    double l=0,r=1e5;
    while(r-l>1e-6)
    {
    	//cout<<l<<' '<<r<<endl;
        double mid=(l+r)/2.0;
        if(check(a,mid,x,n)) r=mid;
        else l=mid;
    }
    return l;
}
int main()
{
    int a[N],b[N];
    int n,m,x,y;
    cin>>n>>m>>x>>y;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=m;i++)
        cin>>b[i];
    double ans=find(a,x,n)+find(b,y,m);
    printf("%.10lf\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值