[CodePlus2017][11月赛][切题]

似乎找不到什么鸡汤来安慰那个pkuwc滚粗的我

听了大家的成绩大概伤在day1 t2吧 11分 连暴力都不太会啊

不会骗分,不会写暴力,不会总结,不会刷很多很多的题

自己蠢,自己颓,自己背锅。

但是还是会忍不住难受

有些路,可能只有一个人走吧。

 

由于睡不着,就来总结一下CodePlus2017 11月月赛的题吧

前四题挺显然的,就是类似第三题这种字符串中转换的题可以思考用dp的方法做

比较巧妙的是t5 和 t6

在此具体讲下

5109: [CodePlus 2017]大吉大利,晚上吃鸡![最短路+拓扑排序+

传递闭包]

题目大意

官方题解(很良心了)

虽然题目中给定的是无向图,但是实际上我们可以先从 S 出发求一遍最短路,然后问题变成了:“在有向无环图(最短路图)上,求有多少个满足条件的点对 A,B,满足从 S到 T 的所有路径一定经过 A,B 其中一点,并且不存在路径同时经过 A,B ”。

求解这到题目的一个关键点在于: 满足条件的点对 A,B 具有特点:从 S到 A的方案数 × 从 A到 T 的方案数 + 从 S到 B的方案数 × 从 B 到 T 的方案数 == 从 S 到 T 的方案数。

所以在有向无环图上用动态规划求解路径条数,再去掉 A可以到达 B 或 B 可以到达 A 的情况即可求解这到题目。

PS:方案数可能会爆掉怎么办?可以对方案数求余一个大整数,如果觉得不够的话可以求余两个大整数。

定义 F(X)= 从 S 到 X的方案数 ×从 X到 T的方案数 = 从 S 经过 X 到达 T 的方案数,所以满足条件的点对 A,B 为:

F(A)+F(B)=F(T)
A和 B 不能相互到达
对于条件 1 ,我们可以使用数据结构进行优化(使用std::map即可),而对于条件 2 ,我们可以使用 bitset 位压进行加速,使得最终时间和空间都能够承受。

时间复杂度: O(nlogn+nmw),其中 w是位压的字长。

讲一点实现上的知识点

1、判断DAG上两点是否可达:

可以用拓扑序+传递闭包(bitset优化解决)(f[i][j]=1代表i到j可达)(n*n/32的时间复杂度和空间复杂度)

代码

void bfs1(int x1)
{
	f2[x1]=1; du1[x1]=0; q2.push(x1);
	while (!q2.empty())
	{
		int x=q2.front(); q2.pop(); b[x][x]=1; 
    	for (int i=head3[x];i;i=next3[i])
	     {
	     	int v=to3[i];  b[v]|=b[x]; //b[a][b]==1时a能到达b 传递闭包bitset优化
	     	du1[v]--; f2[v]+=f2[x],f2[v]%=mod; //f1起点到每个点条数 
	     	if (du1[v]==0) { q2.push(v); }
	     }
    }
}

 贴个模板题

注意这里是有向图但不是DAG(可能有环)

这里似乎n*n*n/32 就够 于是直接floyed水过,似乎直接暴力dfs求每个点能到那些点也能过。。。

不过似乎正解是用tarjan缩点变DAG+bitset(如上题)

给个floyd传递闭包的代码段

 for (int i=1;i<=n;i++)
   {
   	  scanf("%s",s+1);
   	  for (int j=1;j<=n;j++)
   	  {if (s[j]=='1' || i==j) f[i][j]=1; } 
	  f[i][i]=1;
   }
   
   for (int i=1;i<=n;i++)
   for (int j=1;j<=n;j++)
   if (f[j][i]==1) f[j]|=f[i];
   for (int i=1;i<=n;i++) ans+=f[i].count();

2、统计

     直接枚举每个A点,统计贡献,然后去掉A点的影响即可 

     这里种数可以取模后用hash或者直接map处理 同时处理一个bitset  f[i][j]==1 表示经过j的最短路种数为i 

代码(bzoj上被卡内存? loj上过了)

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int mod=1000007;
const int maxn=110000;
struct {
	int x,y,z;
}e[maxn];
int x,y,z,len1,head[maxn],vis[maxn],next1[maxn],zhi[maxn],to[maxn],d[maxn],n,m,S,T,tot,tot2,tot3,cnt;
int  head2[maxn],next2[maxn],to2[maxn],head3[maxn],next3[maxn],to3[maxn];
int du[maxn],du1[maxn],f1[maxn],f2[maxn],dis[maxn];
map<int,int> mp;
bitset<51000> b[51000],ff[51000];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
queue<int>q1,q2;
int ans;
inline int read()
{   int x=0,f=1;  char ch;
    ch=getchar();
    while (ch>'9' || ch<'0') {  if (ch=='-') f=-1; ch=getchar(); }
	while (ch<='9'&& ch>='0') {  x=x*10+ch-'0'; ch=getchar(); }
	return f*x; 
}
void add(int x,int y,int z)
{
    next1[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
    zhi[tot]=z;
}
void add1(int x,int y)
{
    next2[++tot2]=head2[x];
    head2[x]=tot2;
    to2[tot2]=y;
}
void add2(int x,int y)
{
    next3[++tot3]=head3[x];
    head3[x]=tot3;
    to3[tot3]=y;
}
void dij()
{
	memset(d,0x3f,sizeof(d));
	d[S]=0; vis[S]=1;
	q.push(make_pair(d[S],S));
   	while (!q.empty())
	{
	  int k=q.top().first; int x=q.top().second; q.pop();
	   if (vis[x])
	   {
	   	  for (int i=head[x];i;i=next1[i])
	   	  {
	   	  	int v=to[i];
	   	  	if (d[v]>d[x]+zhi[i]) { d[v]=d[x]+zhi[i]; if (vis[v]==0) vis[v]=1,q.push(make_pair(d[v],v));} 
	   	  }
	   	  vis[x]=0;
	   }
	}
}
void bfs(int x)
{
	f1[x]=1; du[x]=0; q1.push(x);
	while (!q1.empty())
	{
		 
		int x=q1.front();q1.pop(); 
	for (int i=head2[x];i;i=next2[i])
	     {
	     	int v=to2[i]; 
	      du[v]--; f1[v]+=f1[x],f1[v]%=mod; //f1起点到每个点条数 
	     	if (du[v]==0) { q1.push(v);dis[v]=dis[x]+1; }
	     }
    }
}
void bfs1(int x1)
{
	f2[x1]=1; du1[x1]=0; q2.push(x1);
	while (!q2.empty())
	{
		int x=q2.front(); q2.pop(); b[x][x]=1; 
    	for (int i=head3[x];i;i=next3[i])
	     {
	     	int v=to3[i];  b[v]|=b[x]; 
	     	du1[v]--; f2[v]+=f2[x],f2[v]%=mod; //f1起点到每个点条数 
	     	if (du1[v]==0) { q2.push(v); }
	     }
    }
}
signed main()
{ 
   n=read(); m=read(); S=read();T=read();
   for (int i=1;i<=m;i++)
   {
   	  x=read();y=read();z=read();
   	  e[i].x=x; e[i].y=y; e[i].z=z;
      add(x,y,z); add(y,x,z);
   }
   dij();
  for (int i=1;i<=m;i++)
   {
   	    if (d[e[i].y]-d[e[i].x]==e[i].z) add1(e[i].x,e[i].y),du[e[i].y]++,add2(e[i].y,e[i].x),du1[e[i].y]++;
      	else if (d[e[i].x]-d[e[i].y]==e[i].z) add1(e[i].y,e[i].x),du[e[i].x]++,add2(e[i].x,e[i].y),du1[e[i].x]++;
   }
   bfs(S);
   bfs1(T);
   if (f1[T]==0)  {  cout<<n*(n-1)/2<<endl; return 0; }
   else
   {
   	for (int i=1;i<=n;i++)
   {
   	  if (mp[f1[i]*f2[i]%mod]==0) mp[f1[i]*f2[i]%mod]=++cnt;
   	  ff[mp[f1[i]*f2[i]%mod]][i]=1;
   }
   for (int i=1;i<=n;i++)
   {
   	  int j=mp[(mod+(f1[T]-f1[i]*f2[i])%mod)%mod];//对于每一种可能的 f1[T]-f1[i]*f2[i]中i的贡献单独计算,之后这个值中的i不存在 
	  ans+=((~b[i])&ff[j]).count();
   	  ff[mp[(f1[i]*f2[i])%mod]][i]=0;
   }
    cout<<(ans%mod+mod)%mod<<endl;
   }
} 

T6也挺好玩哒 

题意:n<=500000个数字,问有多少个区间的众数出现次数严格大于区间长度的一半。

 一种思路:首先明确一性质 每一个区间里只有严格一个众数  所以我们可以对于每个众数来计算贡献

这时候对于每个区间重要的只有这个枚举的数的出现次数

用一个常用思路

将序列中所有等于这个值的位置标为1,其余位置标为-1。那么如果一段区间的和大于0,它就是合法区间。用树状数组优化
显然这样维护的时间复杂度是o(cnt*n*logn)的

考虑怎么优化,我们转换一下题意:直接看大神的题解

现在我们要一次性处理红色括号内的-1对答案的贡献。也就是区间右端点R在这些-1里的时候,有多少个合法的左端点L。

根据数字出现位置,假设它出现了k次,则可以将序列划分成k+1段递减的等差数列,显然同一段等差数列之间不会有任何贡献。

所以我们把问题转换成两步

1、算这一段连续区间的贡献

假设到红括号之前为止,前面的数前缀和为sum。那么对于第一个-1,红括号之前有多少个sum[L-1]属于(-oo,sum-2],它就对答案有多少贡献;对于第二个-1,红括号之前有多少个sum[L-1]属于(-oo,sum-3],它就对答案有多少贡献(为什么不算进第一个-1,因为很明显左端点L不可能取这个-1);依此类推……。

2、用这一段连续区间更新树状数组

//同理可自己想

于是我们变成了在一段区间内加减一段等差数列+相等数列,以及查询和操作,自然可以用线段树维护

不过这里要讲到的是一种巧妙用树状数组维护的方法

我们用a[k]表示满足(2Si)-i<=k的个数 树状数组f[i]维护a[i]

我们考虑树状数组怎么维护区间加同一个数:见链接

而由于本题加的数列特殊性 可以转变为+(一段等差数列-一段等差数列)

区间维护+等差数列相当于上述链接做二次差分

代码[学习来的]

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=1100000;
int next1[maxn],head[maxn],a[maxn],fa[maxn],fb[maxn],fc[maxn],ans,n,type,lim,p,w[maxn],vis[maxn];
void add(int x,int p)
{
	for (int i=x;i<=lim;i+=i&(-i))
	{
		fa[i]+=p; fb[i]+=x*p; fc[i]+=(x-1)*(x-2)*p;
	}
}
int ask(int x)
{
	int a=0,b=0,c=0;
	for (int i=x;i>0;i-=i&(-i))
	{
		a+=fa[i],b+=fb[i],c+=fc[i];
	}
	return (x*x+3*x)*a-2*b*x+c; 
}
void work(int l,int r,int zhi,int t)
{
	int l1,r1;
	l1=zhi-(r-l)+n+1; 
	r1=zhi+n+1;
	if (t==-1)  {add(r1+1,1);add(l1,-1);} 
	else { 
	        ans+=ask(r1-1)-ask(l1-2);//严格小于
            add(r1+1,-1);add(l1,1);
	     } 
}
signed  main()
{
	scanf("%lld%lld",&n,&type);
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		next1[i]=head[a[i]];
		head[a[i]]=i;
	}
	lim=n*2;
	for (int i=n;i>=1;i--)
	{
		if (!vis[a[i]])
		{
			p=0; w[0]=n+1; 
			for (int j=head[a[i]];j;j=next1[j]) w[++p]=j;
			w[++p]=0; 
		    for (int j=p;j;j--)//处理 
	        {
	        	work(w[j],w[j-1]-1,2*(p-j+1)-w[j],1);
			}
			for (int j=p;j;j--)//清空 
	        {
	        	work(w[j],w[j-1]-1,2*(p-j+1)-w[j],-1);
			}
			vis[a[i]]=1;
		}
	}
	cout<<ans/2<<endl; 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章