北京Day 18

今天极其颓废。

T1

题目大意:有一个长度为 n 的数组 A,计算出所有的子段和,总共 n*(n+1)/2 个子段,然后把这些结果排序,构成一个新的数组 B,下标从 1 到 n∗(n+1)/2 。有 Q 个询问,每一个询问给出 Li,Ri ,计算从 Bi 到 Bj 的和。n<=2e5,Q<=20,Ai<=100。

题解:答案等于前j项和减去前i-1项和。对于原数组 A,当子段起点一定的时候,随着右端点的右移,子段和变大。故可以二分一个界限 LIM,然后计算一下 A 中有哪些子段和是<=LIM 的,如果刚好有 k 个,就把所有<=LIM 的子段和加起来就是答案了。如果<=LIM 不刚刚好是 k 个,拿掉若干个等于LIM 使答案达到 k 个的情况。

T1AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
#include<set>
#include<map>
#define int long long 
using namespace std;
inline int re_ad()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
	return x*f;
}
int n,T,a[200010],sum[200010],L,R,Lnum,Rnum;
long long ans;
inline bool check(int x,int r,long long &su)
{
	register int i,j=1,ret=0,tot=0;su=0;
	long long Sum=0,las=0;
	for(i=1;i<=n;++i)
	{
	las=sum[i-1];
	while(j<=n&&sum[j]-las<=x)Sum+=sum[j++];
	tot+=j-i;
	if(sum[j-1]-las==x)++ret;
	su+=Sum-1ll*(j-i)*las;
	Sum-=sum[i];
	}
	if(tot<r)return true;
	if(tot-r<ret){su-=1ll*(tot-r)*x;return true;}
	return false;
}
inline long long solve(int x)
{
	register int l,r,mid,ret=0;
	l=1;r=sum[n];	
	long long tmp=0;
	while(l<=r)
	{
	mid=(l+r)>>1;
	if(check(mid,x,tmp))l=mid+1,ret=tmp;else r=mid-1;
	}
	return ret;
	
}
signed main()
{
	register int i,l,r,mid,Q,x,ls,las,rs;
	T=re_ad();
	while(T--)
	{
	n=re_ad();Q=re_ad();
	for(i=1;i<=n;++i)a[i]=re_ad(),sum[i]=sum[i-1]+a[i];
	while(Q--)
	{
	L=re_ad();R=re_ad();
	ans=solve(R)-solve(L-1);
	cout<<ans<<endl;
	}
	}
	
}

T2

题目大意:给一棵 n 个点的树,边的颜色非蓝即红。对于树上的一条路径,如果该路径上的所有边均为蓝色,则这条路径是危险的,否则是安全的。你想从树上无序挑出三个点,使得它们任意两点之间的路径都是安全的。 问有多少种选择方案,结果对 1e9+7 取模。n<=50000。

题解:用蓝色边将原图连成若干连通块,取的三个点两两不同块,容斥即可。

T2AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
#include<set>
#include<map>
using namespace std;
inline int re_ad()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
	return x*f;
}
inline bool read()
{char ch=getchar();while(ch!='b'&&ch!='r')ch=getchar();return (ch=='b');}
const int mo=1000000007;
int n,cnt=0,size[50010];
vector<int>g[50010];
bool vis[50010];
long long pre[50010],ni[50010],ans;
void dfs(int x)
{
	register int i,sz=g[x].size(),v;
	vis[x]=true;++size[cnt];
	for(i=0;i<sz;++i)
	{
	v=g[x][i];
	if(vis[v])continue;
	dfs(v);
	}
}
inline long long ksm(long long a,int b)
{
	long long ret=1;while(b){if(b&1)ret=ret*a%mo;b>>=1;a=a*a%mo;}return ret;
}
inline long long c(long long x,long long y){if(y>x)return 0;return pre[x]*ni[y]%mo*ni[x-y]%mo;}
int main()
{
	register int i,u,v;
	register bool op;
	n=re_ad();
	for(i=1;i<n;++i)
	{
	u=re_ad();v=re_ad();op=read();
	if(op)g[u].push_back(v),g[v].push_back(u);
	}
	for(i=1;i<=n;++i)if(!vis[i])++cnt,dfs(i);
	pre[0]=1;
	for(i=1;i<=n;++i)pre[i]=pre[i-1]*i%mo;
	ni[n]=ksm(pre[n],mo-2);
	for(i=n-1;i>=0;--i)ni[i]=ni[i+1]*(i+1)%mo;
	ans=c(n,3);
	for(i=1;i<=cnt;++i)ans=((ans-c(size[i],3)%mo)+mo)%mo,ans=((ans-c(size[i],2)*(n-size[i])%mo)%mo+mo)%mo;
	cout<<ans<<endl;
	return 0;
}

T3

题目大意:有 N 个小朋友排成一排,编号 1~N。 在某个时刻,会有编号为 xi 的小朋友看到了笑话 li,然后她会把这个笑话讲出来, 与她距离不超过 ki 的小朋友都会听到这个笑话。如果小朋友是第一次听得到这个笑话,那么她会笑的停不下来。否则她会立即停止笑。每次询问某些时刻一段区间内有多少小朋友在笑。n,m,l<=1e5。

题解:用线段树来维护每个小朋友当前的状态。对每个笑话种类使用一个 set 来维护目前为止听过该笑话的小朋友的区间的集合,要求 set 里记录的区间是没有交的。听到了一个笑话,我们在该笑话种类的 set 里插入该区间; 该区间会被原有区间分成若干仹, 将与原有区间有交的那部分在线段树里设为不笑,与原有区间无交的那部分在线段树里设为笑。 最后将 set 里的区间合并,保持 set 中区间无交的性质。

T3AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
#include<set>
#include<map>
using namespace std;
inline int re_ad()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
	return x*f;
}
set<pair<int,int> > S[100010];
int n,m;
struct node{int num,f;}t[400010];
void change(int k,int l,int r,int num)
{
	if(num){t[k].f=1;t[k].num=r-l+1;}
	else t[k].f=2,t[k].num=0;
}
inline void down(int k,int l,int r)
{
	int mid=l+r>>1;
	if(t[k].f==1){change(k<<1,l,mid,1);change(k<<1|1,mid+1,r,1);}
	else {change(k<<1,l,mid,0);change(k<<1|1,mid+1,r,0);}
	t[k].f=0;
}
void chan(int k,int l,int r,int L,int R,int num)
{
	if(L<=l&&r<=R){change(k,l,r,num);return;}
	int mid=l+r>>1;
	if(t[k].f)down(k,l,r);
	if(R<=mid)chan(k<<1,l,mid,L,R,num);
	else if(L>mid)chan(k<<1|1,mid+1,r,L,R,num);
	else chan(k<<1,l,mid,L,R,num),chan(k<<1|1,mid+1,r,L,R,num);
	t[k].num=t[k<<1].num+t[k<<1|1].num;
}
int query(int k,int l,int r,int L,int R)
{
	if(L<=l&&r<=R){return t[k].num;}
	int mid=l+r>>1;
	if(t[k].f)down(k,l,r);
	if(R<=mid)return query(k<<1,l,mid,L,R);
	if(L>mid)return query(k<<1|1,mid+1,r,L,R);
	return query(k<<1,l,mid,L,R)+query(k<<1|1,mid+1,r,L,R);
}
inline void solve(int num,int l,int r)
{
	if(S[num].count(make_pair(l,r))){chan(1,1,n,l,r,0);return;}
	S[num].insert(make_pair(l,r));
	set<pair<int,int> >::iterator i,h,las;register int ll,rr; 
	i=S[num].lower_bound(make_pair(l,r));h=i;
	if(i!=S[num].begin())
	{
	--i;
	if(i->second>=l)
	{rr=min(i->second,r);chan(1,1,n,l,rr,0);l=rr+1;h=i;}
	++i;
	}
	++i;
	while(i!=S[num].end())
	{
	if(r<i->first)break;
	if(l<i->first){chan(1,1,n,l,i->first-1,1);l=i->first;}
	rr=min(i->second,r);
	if(l<=rr){chan(1,1,n,l,rr,0);l=rr+1;}
	++i;
	}
	if(l<=r){chan(1,1,n,l,r,1);}
	i=h;++i;
	ll=h->first;rr=h->second;
	while(i!=S[num].end())
	{
	if(i->first>rr)break;
	rr=max(rr,i->second);
	las=i;++i;
	S[num].erase(las);
	}
	S[num].erase(h);
	S[num].insert(make_pair(ll,rr));
}
int main()
{
	register int i,x,y,l,k,op;
	n=re_ad();m=re_ad();
	for(i=1;i<=m;++i)
	{
	op=re_ad();
	if(op==1){x=re_ad();l=re_ad();k=re_ad();solve(l,max(x-k,1),min(x+k,n));}
	else{x=max(re_ad(),1);y=min(n,re_ad());printf("%d\n",query(1,1,n,x,y));}
	}
}
​

T4

题目大意:有 n 个岛屿,规划建设 m 座桥,第 i 座桥的成本为 zi,有 pi 的概率不能建造。求在让岛屿尽量联通的情况下,期望最小成本为多少。

题解:

子集DP。
用fi,j表示子集为i,只考虑前j条边的情况下连通并不与其他任何点连通的概率。
用gi,j表示子集为i,只考虑前j条边的情况下连通并不与其他任何点连通的期望边权和。

之后从小到大枚举每一条边,按照是否可以连通分类讨论。
如果不能连通,那么所有只包含第一个点或只包含第二个点的子集都需要将概率和期望边权和都乘上该边不连通的概率。 

如果连通,那么我们将所有包含第一个点和包含第二个点的不相交的子集连起来。
这里要注意,如果这两个子集之间还有更小的边,那么这条边不连通的代价在两个子集中都计算过,所以我们要将这部分除去。

这部分我们可以用一个数组hi来维护i这个子集内部所有边不连通的概率,那么对于两个合并的子集x和y,我们只要用h(x∪y) /(hx hy)就可以算出x和y之间的所有不连通的边的概率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值