Tishreen-CPC 2018 G~L

Tishreen-CPC 2018 G~L

G. Colors Overflow

给出一条线段,每个数据有两种操作:
1,l,r将l,r之间的每一个子段加上一种颜色
2,l,r询问l,r之间的总共有几种颜色
可以想到记录从开始位置每一种颜色的左端点与右端点总数,对于i位置左端点总数意味着到i位置为止目前总共有几种颜色存在过,右端点总数意味着已经有几种颜色已经结束了,以后不会再出现;
那么,[l,r]区间上的颜色总数就是 到r总共有几个开始过 减去 到l已经结束的个数
于是,开始维护左右端点的个数.但是对于多点不同的修改如果每次都一个个修改过去哪怕是树状数组也会TLE(单次修改复杂度为O(len*log(len))查询各方题解看到分块的思想进行优化。、
大体思路为:
虽然是多点修改但是对于整个修改区间是呈现等差数列的形式,所以对于总的区间我们将其分成好几块,当修改操作的区间大小符合某一块的大小时,用等差数列求和的方式优化求0到i位置的端点数的过程将单点操作转化为某一块的区间操作
为了简化写代码的过程,因为维护左区间的过程和维护右区间的过程对称,所以,将区间翻转可以重复利用已经写好的代码(只需稍微修改)。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
inline long long read()
{
	long long kk=0,f=1;
	char cc=getchar();
	while(cc<'0'||cc>'9'){if(cc=='-')f=-1;cc=getchar();}
	while(cc>='0'&&cc<='9'){kk=(kk<<1)+(kk<<3)+cc-'0';cc=getchar();}
	return kk*f;
}
const int bsz=350,N=1e5+7,n=1e5+2;
int bn,bl[N],br[N],sz[N];
int qop[N],ql[N],qr[N];
LL ans[N],bs[N],bb[N],bd[N],a[N];
void init(int n)//按照最坏情况分成bsz个区间 
{
    bn=(n-1)/bsz+1;
    for (int i=0;i<bn;++i)
    {
        bl[i]=i*bsz;
        br[i]=min(n,(i+1)*bsz);
        sz[i]=br[i]-bl[i];
        bs[i]=bd[i]=bb[i]=0;
    }
    for (int i=0;i<n;++i)
        a[i]=0;
}
void build(int b)//当前区间内 
{
    for (int i=bl[b];i<br[b];++i)
        a[i]+=bb[b]+(i-bl[b]+1)*bd[b];
    bb[b]=bd[b]=0;
}
void update(int b,int l,int r)
{
    int nl=max(l,bl[b]),nr=min(br[b],r);
    if(nl>=nr)
        return;
    if(nr-nl==sz[b])//因为是一整块,所以可以用完整的求和公式来维护 
    {
        bb[b]+=nl-l;//维护这一块的基数避免不是从0开始的情况 
        bd[b]++;//维护这一块的公差
        bs[b]+=(1+sz[b])*sz[b]/2+(nl-l)*sz[b];//用求和公式维护这一块端点数
        return;
    }
    for(int i=nl;i<nr;++i)//如果该块没有被包含,则直接暴力维护
    {
        bs[b]+=i-l+1; 
        a[i]+=i-l+1;
    }
    return;
}
LL query(int b,int l,int r)
{
    int nl=max(l,bl[b]),nr=min(br[b],r);
    if(nl>=nr)
        return 0;
    if(nr-nl==sz[b])//被包含直接返回这一块的值
        return bs[b];
    //没有被包含时
    LL res=0; 
    build(b);//通过之前维护的公差和基数暴力统计端点数
    for(int i=nl;i<nr;++i)res+=a[i];
    return res;
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int q;
        cin>>q;
        memset(ans,0,sizeof(ans)); 
        for(int i=0;i<q;++i)
        {
            cin>>qop[i]>>ql[i]>>qr[i];
            --ql[i];
            --qr[i];
        }
        init(n);//分块 
        for(int i=0;i<q;++i)//右端点的减操作 
        {
            if(qop[i]==1)//修改第j块从ql[i]到qr[i]+1的内容
            for(int j=0;j<bn;++j)
            update(j,ql[i],qr[i]+1);
            else 
            for(int j=0;j<bn;++j)//询问每一块的从0到ql[i]端点数 
            ans[i]-=query(j,0,ql[i]);    
            ql[i]=n-1-ql[i];
            qr[i]=n-1-qr[i];
            swap(ql[i],qr[i]);//旋转
        }
        init(n);//重新分块 
        for(int i=0;i<q;++i)//和上面差不多 左端点的加操作 
        {
            if(qop[i]==1)
            for(int j=0;j<bn;++j)
            update(j,ql[i],qr[i]+1);
            else
            {
                for(int j=0;j<bn;++j)
                ans[i]+=query(j,ql[i],n);
                cout<<ans[i]<<endl;
            }
        }       
    }
    return 0;
}

H. Don’t Ever Ask a Girl for her Codeforces Account

给出一棵树(没指出根结点),两个结点A、B,现在要从A到C,B到D,要求途经的结点没有重复。问这样的C、D的对数。
(我们假设以A为根),可以想到,如果将树分为两棵子树,每次各自取一个结点,可以取到所有可能,但是可能存在重复。所有取子树的策略要保证不重复取。可以把含A与含B作为区分两棵子树的标准,那么以此从A到B的路径就是取不同子树的方案判断集合。
1,我们先利用时间戳求出每一个结点下(含本身)的结点总数。
2,对于每一个从b到a的途经结点对其子节点进行操作3。
3,按照子树含B和不含B分成两棵子树。在单次次取结点的过程中,B取含B的子树的所有结点,A只取该结点以下的不含B的子树以避免重复取。本次操作的方案数也就是num[含B]*num[不含B]
4,总方案为所有方案总和

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
inline long long read()
{
	long long kk=0,f=1;
	char cc=getchar();
	while(cc<'0'||cc>'9'){if(cc=='-')f=-1;cc=getchar();}
	while(cc>='0'&&cc<='9'){kk=(kk<<1)+(kk<<3)+cc-'0';cc=getchar();}
	return kk*f;
}
int n,ca,cb,fath[100011];LL tot=0;
LL tim=0,ru[100011],num[100011];
list<int>edge[100011];
void add(int a,int b)
{
	edge[a].push_back(b);
	edge[b].push_back(a);
}
void dfs(int a,int fa)
{
	ru[a]=tim++;
	fath[a]=fa;
	list<int>::iterator it=edge[a].begin();
	for(;it!=edge[a].end();++it)
	{
		if(*it!=fa)
		{
			dfs(*it,a);
		}
	}
	num[a]=tim-ru[a];
}
void dfs2(int a)
{
	if(a==ca)return;
	int li=fath[a];dfs2(li);
	tot+=(num[li]-num[a])*num[a];
}
int main()
{
	int T=read();
	while(T--)
	{
		n=read();ca=read();cb=read();tim=0;tot=0;
		memset(ru,0,sizeof(ru));
		memset(num,0,sizeof(num));
		memset(fath,0,sizeof(fath));
		for(int i=0;i<=n+10;++i)edge[i].clear();
		for(int i=1;i<n;++i)
		{
			add(read(),read());
		}
		dfs(ca,0);
		dfs2(cb);
		cout<<tot<<endl;
	}
 }

I. Odd and Even Queries

给出n个数,q个询问:
询问0,要求求出在l,r之间总共有多少满足元素之积为偶数的子序列
询问1,要求求出在l,r之间总共有多少满足元素之积为奇数的子序列
显然,有一个数为偶数,总的积就是偶数。
所以,对于询问0,答案是 $ (2{偶数个数}-1)*2{奇数个数}$
对于询问1,答案是 2 奇 数 个 数 − 1 2^{奇数个数}-1 21

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
inline long long read()
{
	long long kk=0,f=1;
	char cc=getchar();
	while(cc<'0'||cc>'9'){if(cc=='-')f=-1;cc=getchar();}
	while(cc>='0'&&cc<='9'){kk=(kk<<1)+(kk<<3)+cc-'0';cc=getchar();}
	return kk*f;
}
const LL mod=1e9+7;
LL n,q,ji,ou,l,r,t;
bool ans[200022];LL sum[200022];
LL qp(LL a,LL b)
{
	LL kk=1;
	while(b)
	{
		if(b&1)kk=kk*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return kk;
}
int main()
{
	int T=read();
	while(T--)
	{
		memset(ans,0,sizeof(ans));
		memset(sum,0,sizeof(sum));
		n=read();q=read();
		for(int i=1;i<=n;++i)ans[i]=(read()&1);
		for(int i=1;i<=n;++i)sum[i]=(sum[i-1]+ans[i]);
		for(int i=1;i<=q;++i)
		{
			l=read();r=read();t=read();
			ji=sum[r]-sum[l-1];ou=r-l+1-ji;
			if(t==0)
			{
				cout<<((qp(2,ou)-1)*qp(2,ji))%mod<<endl;
			}
			else 
			{
				cout<<(qp(2,ji)-1+mod)%mod<<endl;
			}
		}
	}
} 

J. Weird Sum

每次给出一个数组a,对于其中的子序列(i,j)来说M是a数组i到j中的最大值,如果a[i] xor a[j] >M,那么将M加到总和里面,最后输出所有满足条件的M的总和。
emm求助各方大佬

K. Quantum Stones

图上有n个城市m条边,给出k个石头,石头用来减少路程损耗,可以用一块的石头来将某条边的费用x变为2*k+x/k(co一定要为x的因子且为素数)问,最小路程损耗是多少
经过大佬提点,看了分层图最短路的相关文章分享两篇
link
link
当前这个题目就差不多类似打折的情况,也就是消耗一次机会后添加的新边边权值不是0。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,int> pii;
inline long long read()
{
	long long kk=0,f=1;
	char cc=getchar();
	while(cc<'0'||cc>'9'){if(cc=='-')f=-1;cc=getchar();}
	while(cc>='0'&&cc<='9'){kk=(kk<<1)+(kk<<3)+cc-'0';cc=getchar();}
	return kk*f;
}
vector<pii> edge[10005*101];
int n,m,k,st,en;bool vis[10005*101];LL dis[10005*101];
const LL maxL=0x3f3f3f3f3f3f3f3fll;
LL dij()
{
    priority_queue<pii,vector<pii>,greater<pii> > Q;
    Q.push(make_pair(0ll,st));
    dis[st]=0;
    while(!Q.empty())
    {
        int now=Q.top().second;Q.pop();
        if(vis[now]) continue;
        if(now%n==en%n) return dis[now];
        vis[now]=1;
        for(int i=0;i<edge[now].size();++i)
		{
			int to,wei;to=edge[now][i].second;wei=edge[now][i].first;
			if(dis[to]>dis[now]+wei)
			{
				dis[to]=dis[now]+wei;
				Q.push(make_pair(dis[to],to));vis[to]=0;
			}
		}
    }
    return maxL;
}
int main()
{
	int T=read();
	while(T--)
	{
		n=read();m=read();k=read();
		for(int i=0;i<=n*(k+1);++i)
		{
			edge[i].clear();dis[i]=maxL;vis[i]=0;
		}
		for(int i=1;i<=m;++i)//建图
		{
			int a,b,w;a=read();b=read();w=read();
			for(int j=0;j<=k;++j)//对每一条边先复制k+1层
			{
				edge[a+j*n].push_back(make_pair(w,b+j*n));
                edge[b+j*n].push_back(make_pair(w,a+j*n));
			}
			int mi=0x3f3f3f3f,li=w;
            for(int j=2;j*j<=li;++j)//对这条边查找符合条件的的最优情况
            if(li%j==0) 
            {
                mi=min(mi,2*j+w/j);
                while(li%j==0)li/=j;
            }
            if(li>1)mi=min(mi,2*li+w/li);
            if(mi<w)//用了石头后花费更小
            for(int j=0;j<k;++j)
            {//连边指向下一层,代表用一个石头的代价换取这个新的边权
            	edge[a+j*n].push_back(make_pair(mi,b+n*(j+1)));
            	edge[b+j*n].push_back(make_pair(mi,a+n*(j+1)));
			}
		}
		st=read();en=read();
		LL asd=dij();
		if(asd==maxL)printf("-1\n");
		else printf("%lld\n",asd);
	}
}

L. Odd and Even Count

求1~n中有几个奇数几个偶数
详见代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
inline long long read()
{
	long long kk=0,f=1;
	char cc=getchar();
	while(cc<'0'||cc>'9'){if(cc=='-')f=-1;cc=getchar();}
	while(cc>='0'&&cc<='9'){kk=(kk<<1)+(kk<<3)+cc-'0';cc=getchar();}
	return kk*f;
}
int main()
{
	int T=read();while(T--)
	{
		int n=read();
		if(n&1)printf("%d %d\n",(n/2)+1,(n/2));//对于奇数
		else printf("%d %d\n",n/2,n/2);//对于偶数
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值