似乎找不到什么鸡汤来安慰那个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;
}