A
由于并不需要使操作次数尽可能少,所以还是很容易构造的。
直接将每个左括号放到前 n n n 位,然后剩下右括号就在后 n n n 位,这就合法了。
代码如下:
#include <cstdio>
#include <cstring>
int n,now=0;
char s[200010];
int main()
{
scanf("%s",s+1); n=strlen(s+1);
printf("%d\n",n/2);
for(int i=1;i<=n;i++)
if(s[i]=='(')printf("%d %d\n",++now,i);
}
B
找负环理所当然地要请出Bellman-Ford算法,由于边上还有一个系数,所以数组设成三维: f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 表示从 1 1 1 出发至多走 i i i 步到 j j j 并且系数为 k k k 的最短路,那么从 1 1 1 出发至多走 i i i 步到 j j j 的最短路长度就是 min { f [ i ] [ j ] [ k ] + k x } \min\{f[i][j][k]+kx\} min{f[i][j][k]+kx}。
那么可以得到出现负环的要求:存在某个点满足
min
{
f
[
n
]
[
i
]
[
k
]
+
k
x
}
<
min
{
f
[
n
−
1
]
[
i
]
[
j
]
+
j
x
}
\min\{f[n][i][k]+kx\}<\min\{f[n-1][i][j]+jx\}
min{f[n][i][k]+kx}<min{f[n−1][i][j]+jx}
由于我们需要的是不存在负环,所以不等式变为:
min
{
f
[
n
]
[
i
]
[
k
]
+
k
x
}
≥
min
{
f
[
n
−
1
]
[
i
]
[
j
]
+
j
x
}
\min\{f[n][i][k]+kx\}\geq\min\{f[n-1][i][j]+jx\}
min{f[n][i][k]+kx}≥min{f[n−1][i][j]+jx}
下面对于每个 i i i 求出这个不等式的解集即可。
首先枚举
k
k
k,则变为求这个不等式的解集:
f
[
n
]
[
i
]
[
k
]
+
k
x
≥
min
{
f
[
n
−
1
]
[
i
]
[
j
]
+
j
x
}
f[n][i][k]+kx \geq\min\{f[n-1][i][j]+jx\}
f[n][i][k]+kx≥min{f[n−1][i][j]+jx}
因为 f [ n ] [ i ] [ k ] + k x f[n][i][k]+kx f[n][i][k]+kx 要大于右边的最小值,也就是说,大于其中任意一个即可,于是我们再枚举 j j j 求出每个不等式的解集,然后求个并集即可。
再看回一开始的不等式, min { f [ n ] [ i ] [ k ] + k x } ≥ min { f [ n − 1 ] [ i ] [ j ] + j x } \min\{f[n][i][k]+kx\}\geq\min\{f[n-1][i][j]+jx\} min{f[n][i][k]+kx}≥min{f[n−1][i][j]+jx},左边的最小值要大于右边的最小值,也就是说,对于每一个 k k k,左边的柿子都要大于右边的最小值,所以我们将上面求出来的并集们再求个交集就得到解集了。
最后求解时,因为如果一个点在负环内,这个点能到达的所有点都会受到影响,所以对于每个 i i i,求解时还需要将能到达 i i i 的所有点的解集求个交集,才能得到最后的答案。
说的简单,事实上,你会发现网上的大佬们都用到了补集。回顾上面的流程:
- 枚举 k k k
- 枚举 j j j
- 求 f [ n ] [ i ] [ k ] + k x ≥ f [ n − 1 ] [ i ] [ j ] + j x f[n][i][k]+kx \geq\ f[n-1][i][j]+jx f[n][i][k]+kx≥ f[n−1][i][j]+jx 的解集
- 将 3 3 3 中的解集求并集
- 将 4 4 4 中的解集求交集
- 求解:将能到达 i i i 的所有点的解集求交集
我们发现, 3 3 3 中求出来的解集都形如 x ≤ p x\leq p x≤p 或 x ≥ p x\geq p x≥p,求并集之后就形如 ( − ∞ , t ) ∪ ( t ′ , ∞ ) (-\infty,t)\cup(t',\infty) (−∞,t)∪(t′,∞),然后发现,第 5 5 5 步很难做,更别说第 6 6 6 步了。
于是这时候用到了补集,我们将第 4 4 4 步中求出来的并集求个补集,变成 [ t , t ′ ] [t,t'] [t,t′],然后都存起来,跳过第 5 5 5 步,直接到第 6 6 6 步,将能到达 i i i 的所有点的解集的补集们聚在一起,然后求出他们的并集的补集即可,具体操作就见代码了。
补充一句:其实代码中在第 3 3 3 步就求补集了qwq,并没有等到第 4 4 4 步(因为都是借鉴网上大佬的嘛),求了补集之后第 4 4 4 步就应该求交集而不是并集了。
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 110
#define f(i,j,k) f[i][j][k+105]
#define inf 999999999999999999
#define ll long long
int n,m,len;
struct edge{int x,y,z,k;};
edge e[maxn*maxn];
void buildroad(int x,int y,int z,int k){e[++len]=(edge){x,y,z,k};}
ll f[maxn][maxn][maxn<<1];//注意,第三维可能是负数,所以要将第三维整体向右挪
int g[maxn][maxn];//用来判断某两个点之间能否到达
void floyd()
{
for(int i=1;i<=n;i++)g[i][i]=1;
for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)g[i][j]|=g[i][k]&g[k][j];
}
void bellman_ford()
{
for(int i=0;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=-n;k<=n;k++)f(i,j,k)=inf;
f(0,1,0)=0;
for(int i=1;i<=n;i++)
{
memcpy(f[i],f[i-1],sizeof(f[i]));
for(int j=1;j<=m;j++)
for(int k=-n;k<=n;k++)
if(f(i-1,e[j].x,k)<inf)
f(i,e[j].y,k+e[j].k)=min(f(i,e[j].y,k+e[j].k),f(i-1,e[j].x,k)+e[j].z);
}
}
struct par{//pair用不习惯qwq,还是自己写的好
ll x,y;
par(ll xx,ll yy):x(xx),y(yy){}
bool operator <(const par &b)const{return x==b.x?(y<b.y):(x<b.x);}
};
vector<par> a[maxn],s;
void solve()
{
for(int i=1;i<=n;i++)
{
for(int k=-n;k<=n;k++)
if(f(n,i,k)<inf)
{
ll l=-inf,r=inf;
for(int j=-n;j<=n;j++)
if(f(n-1,i,j)<inf)
{
if(k>j)r=min(r,(ll)ceil(1.0*(f(n-1,i,j)-f(n,i,k))/(k-j)));
else if(k<j)l=max(l,(ll)floor(1.0*(f(n-1,i,j)-f(n,i,k))/(k-j)));
else if(f(n,i,k)>=f(n-1,i,j)){l=inf,r=-inf;break;}
//k=j意味着方程中x的系数为0,如果此时柿子仍然成立,那么说明对于当前k
//找到了一个j,满足无论x是多少柿子都成立,所以不用再继续往下找了,解集的补集必然为空集
}
if(l<r)a[i].push_back(par(l,r));
//如果l>=r,那么这个补集就是空的,对后面求并集没有任何用
}
}
}
void getans()
{
for(int i=1;i<=n;i++)
{
ll l=inf,r=-inf,maxr=-inf;s.clear();
//maxr记录目前最靠右的右端点
for(int j=1;j<=n;j++)if(g[1][j]&&g[j][i])
for(int k=0;k<a[j].size();k++)s.push_back(a[j][k]);//收集所有能到i的点的补集们
sort(s.begin(),s.end());//以左端点为第一关键字,右端点为第二关键字,从小到大排
for(int j=0;j<s.size();j++)
{
//很显然最后的解集只有一段,不可能形如[l,r]∪[l',r'],所以找到一段解集就可以break了
if(j==0&&s[j].x>-inf){l=-inf,r=s[j].x;break;}
//假如(-∞,s[0].x)没有被任何补集覆盖,那么这就是答案了
if(maxr!=-inf&&maxr<=s[j].x){l=maxr,r=s[j].x;break;}
//判断中间是否空出一段没被覆盖
maxr=max(maxr,s[j].y);
}
if(r==-inf&&maxr<inf)l=maxr,r=inf;
if(l==-inf||r==inf||!s.size())printf("-1\n");
else printf("%lld\n",max(r-l+1,0ll));
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1,x,y,z,k;i<=m;i++)
scanf("%d %d %d %d",&x,&y,&z,&k),buildroad(x,y,z,k),g[x][y]=1;
floyd();bellman_ford();
solve();getans();
}
C
先转化成求 f ( i ) = ∑ u , v [ i ∣ f ( u , v ) ] f(i)=\sum_{u,v}[i|f(u,v)] f(i)=∑u,v[i∣f(u,v)],即求出有多少个 f ( u , v ) f(u,v) f(u,v) 是 i i i 的倍数,然后最后搞一搞就可以求出有多少个 f ( u , v ) f(u,v) f(u,v) 恰好为 i i i。
考虑根号分治,对于 d > n d>\sqrt n d>n,设 h i , j h_{i,j} hi,j 表示 i i i 子树内深度为 j j j 的点数( i i i 的深度为 0 0 0),每次启发式合并子树的 h h h,发现当自己之前的子树中最大深度大于 n \sqrt n n 且子树的最大深度大于 n \sqrt n n 时,深度的 gcd \gcd gcd 才可能 > n >\sqrt n >n,此时暴力枚举每个 d > n d>\sqrt n d>n 更新 f f f 即可。由于都大于 n \sqrt n n 的合并次数不可能超过 n \sqrt n n 次,所以时间复杂度为 n n log n n\sqrt n\log n nnlogn。
而对于 d ≤ n d\leq \sqrt n d≤n,这样的 d d d 不超过 n \sqrt n n 个,直接 d d d 次 O ( n ) O(n) O(n) 的 dp \text{dp} dp 对于每个 d d d 求解即可,具体参考代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010
#define ll long long
int n,fa[maxn],dep[maxn];
ll f[maxn],dirf[maxn];
const int block=150;
void solve1(){
static int g1[maxn],g2[maxn],upto[maxn];
for(int i=1;i<=n;i++)upto[i]=i;
for(int d=1;d<=min(block,n);d++){
memset(g1,0,sizeof(g1));
memset(g2,0,sizeof(g2));
for(int i=n;i>1;i--){
g1[i]++;
if(upto[i]!=1){
g2[upto[i]]+=g1[i];
upto[i]=fa[upto[i]];
}
f[d]+=1ll*g1[fa[i]]*g2[i];
g1[fa[i]]+=g2[i];
}
}
}
void solve2(){
static vector<int> h[maxn];
for(int i=n;i>=1;i--){
h[i].push_back(1);
if(h[fa[i]].size()<h[i].size())h[fa[i]].swap(h[i]);
int *a=h[fa[i]].data(),a_n=h[fa[i]].size();
int *b=h[i].data(),b_n=h[i].size();
if(a_n>block&&b_n>block){
for(int d=block+1,lim=min(a_n,b_n);d<=lim;d++){
int tot1=0,tot2=0;
for(int i=d;i<=a_n;i+=d)tot1+=a[a_n-i];
for(int i=d;i<=b_n;i+=d)tot2+=b[b_n-i];
f[d]+=1ll*tot1*tot2;
}
}
for(int i=1;i<=b_n;i++)a[a_n-i]+=b[b_n-i];
}
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)scanf("%d",&fa[i]);
for(int i=2;i<=n;i++)dep[i]=dep[fa[i]]+1,dirf[dep[i]]++;
for(int i=n-1;i>=1;i--)dirf[i]+=dirf[i+1];
solve1();solve2();
for(int i=n;i>=1;i--)for(int j=i+i;j<=n;j+=i)f[i]-=f[j];
for(int i=1;i<n;i++)printf("%lld\n",f[i]+dirf[i]);
}