1.队伍统计
Description
现在有n个人要排成一列,编号为 1 → n 1\to n 1→n 。但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有m条矛盾关系(u,v),表示编号为u的人想要排在编号为v的人前面。要使得队伍和谐,最多不能违背k条矛盾关系(即不能有超过k条矛盾关系 ( u , v ) (u,v) (u,v),满足最后v排在了u前面)。问有多少合法的排列。答案对 1 0 9 + 7 10^9+7 109+7取模。
Input
第一行包括三个整数
n
,
m
,
k
n,m,k
n,m,k。
接下来
m
m
m行,每行两个整数
u
,
v
u,v
u,v,描述一个矛盾关系
(
u
,
v
)
(u,v)
(u,v)。
保证不存在两对矛盾关系
(
u
,
v
)
,
(
x
,
y
)
(u,v),(x,y)
(u,v),(x,y),使得
u
=
x
u=x
u=x且
v
=
y
v=y
v=y 。
Output
输出包括一行表示合法的排列数。
Data Constraint
对于
30
%
30\%
30%的数据,
n
≤
10
n\leq10
n≤10
对于
60
%
60\%
60%的数据,
n
≤
15
n\leq15
n≤15
对应
100
%
100\%
100%的数据,
n
,
k
≤
20
,
m
≤
n
∗
(
n
−
1
)
n,k\leq20,m\leq n*(n-1)
n,k≤20,m≤n∗(n−1),保证矛盾关系不重复。
Solutions
看到数据范围,显然就是状压
D
P
DP
DP了。
设
F
[
s
]
[
i
]
F[s][i]
F[s][i] 表示已经选了的人的集合为
s
s
s 、已经违背了
i
i
i 条矛盾关系的合法排列数。
转移时枚举将要选的人,处理出会产生的矛盾关系即可。
那么如何快速处理出将会产生的矛盾关系呢?
考虑预处理出
a
[
x
]
a[x]
a[x]表示排在
x
x
x以前会有矛盾的人的集合,
那么与
s
&
s\&
s& 的值二进制的
1
1
1 的个数就是所求的数量了。
时间复杂度为
O
(
2
N
∗
N
∗
K
)
O(2N∗N∗K)
O(2N∗N∗K)
Code
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int mo = 1e9 + 7;
int n,m,k,ans;
int a[21],g[1 << 20],p[21],f[1 << 20][21];
inline void read(int &sum)
{
char ch = getchar();
int tf = 0;
sum = 0;
while((ch < '0' || ch > '9') && (ch != '-'))
ch = getchar();
tf = ((ch == '-') && (ch = getchar()));
while(ch >= '0' && ch <= '9')
sum = sum * 10 + (ch - 48),ch = getchar();
(tf) && (sum = -sum);
}
inline void dfs(int x,int y,int z)
{
if(z > n)
return;
g[x] = y;
dfs(x,y,z + 1);
dfs(x + p[z],y + 1,z + 1);
}
int main()
{
read(n);
read(m);
read(k);
for(int i = p[0] = 1;i <= n;i++)
p[i] = p[i - 1] << 1;
dfs(0,0,0);
for(int i = 1;i <= m;i++)
{
int x,y;
read(x);
read(y);
a[y] |= p[x - 1];
}
f[0][0] = 1;
for(int s = 0;s < p[n];s++)
for(int j = 1;j <= n;j++)
if(p[j - 1] & s)
{
int sum = g[s&a[j]];
for(int i = sum;i <= k;i++)
f[s][i] = (f[s][i] + f[s - p[j - 1]][i - sum]) % mo;
}
for(int i = 0;i <= k;i++)
ans = (ans + f[p[n] - 1][i]) % mo;
cout << ans;
return 0;
}
2.序列问题
Description
给定一个长度为
n
n
n的序列
A
A
A。定义
f
(
l
,
r
)
=
m
a
x
(
a
l
,
a
l
+
1
,
.
.
.
,
a
r
)
f(l,r) = max(a_l,a_{l+1},...,a_r)
f(l,r)=max(al,al+1,...,ar),
g
(
l
,
r
)
=
m
i
n
(
a
l
,
a
l
+
1
,
.
.
.
,
a
r
)
g(l,r)=min(a_l,a_{l+1},...,a_r)
g(l,r)=min(al,al+1,...,ar),希望你求出:
(
∑
l
=
1
n
∑
r
=
l
n
f
(
l
,
r
)
∗
g
(
l
,
r
)
)
m
o
d
(
1
e
9
+
7
)
\left(\sum\limits^{n}_{l=1} \sum\limits^{n}_{r=l}f(l,r)*g(l,r)\right)mod\left(1e9+7\right)
(l=1∑nr=l∑nf(l,r)∗g(l,r))mod(1e9+7)
Input
首先输入n。
接下来输入n个数,描述序列 A。
Output
输出一行一个整数代表答案。
Data Constraint
对于
30
%
30\%
30%的数据,
n
≤
5000
n\leq5000
n≤5000
对于
60
%
60\%
60%的数据,
n
≤
50000
n\leq50000
n≤50000
对于
100
%
100\%
100%的数据,
n
≤
500000
,
0
≤
A
[
i
]
≤
1
0
9
n\leq500000,0\leq A[i]\leq10^9
n≤500000,0≤A[i]≤109
Solutions
这种题马上想到的就是分治。
对于区间
[
x
.
.
y
]
[x..y]
[x..y],将它分成三部分:
m
=
x
+
y
2
m = \large\frac{x+y}{2}
m=2x+y
1.左右端点都在
[
x
.
.
m
]
[x..m]
[x..m]里的。
2.左右端点都在
[
m
+
1..
y
]
[m + 1..y]
[m+1..y]里的。
3.左右端点在
m
m
m的两旁。
前两个递归处理,考虑第三个怎么求,这是分治的常规套路。
先考虑区间
[
m
+
1..
y
]
[m + 1..y]
[m+1..y](右区间),以
m
+
1
m+1
m+1为左端点,从左往右枚举右端点,
m
i
n
min
min值会不断变小,
m
a
x
max
max值会不断变大,将变化的地方存下来,分别放进两个数组里,设为
a
,
b
a,b
a,b。
现在还要考虑区间
[
x
.
.
m
]
[x..m]
[x..m](左区间),从
m
m
m出发,从右往左枚举左端点l,记录下
m
i
n
min
min值和
m
a
x
max
max值,设为
m
i
n
l
\large min_l
minl。
最后需要将两个区间合并。
在
a
a
a数组里找到代表的值第一个小于
m
i
n
l
min_l
minl的位置
u
u
u(从左往右看),
在
b
b
b数组里找到代表的值第一个大于
m
a
x
l
max_l
maxl的位置
v
v
v(从左往右看)。
这个可以二分。
由于
m
i
n
l
min_l
minl不断缩小,
m
a
x
r
max_r
maxr不断变大,也可以直接维护个指针。
右端点r的取法接下来有四种情况:
1.
r
<
m
i
n
(
u
,
v
)
,
m
i
n
[
l
.
.
r
]
=
m
i
n
l
,
m
i
n
[
l
.
.
r
]
=
m
i
n
r
r < min(u, v),min_{[l..r]} = min_l,min_{[l..r]} = min_r
r<min(u,v),min[l..r]=minl,min[l..r]=minr
2.
r
≥
m
a
x
(
u
,
v
)
,
m
i
n
[
l
.
.
r
]
=
[
l
.
.
r
]
r \geq max(u, v), min_{[l..r]} = [l..r]
r≥max(u,v),min[l..r]=[l..r]里的点到
m
+
1
m+1
m+1的最小值,
m
a
x
[
l
.
.
r
]
=
[
l
.
.
r
]
max_[l..r] = [l..r]
max[l..r]=[l..r]里的点到
m
+
1
m+1
m+1的最大值。
3.
u
≤
v
,
u
≤
r
<
v
,
m
i
n
[
l
.
.
r
]
=
[
l
.
.
r
]
u \leq v, u\leq r < v,min_{[l..r]} = [l..r]
u≤v,u≤r<v,min[l..r]=[l..r]里的点到
m
+
1
m+1
m+1的最小值,
m
a
x
[
l
.
.
r
]
=
m
i
n
r
max_{[l..r]} = min_r
max[l..r]=minr。
4.
u
>
v
,
v
≤
r
<
u
,
m
i
n
[
l
.
.
r
]
=
m
i
n
l
,
m
a
x
[
l
.
.
r
]
=
[
l
.
.
r
]
u >v, v\leq r < u,min_{[l..r]} = min_l,max_{[l..r]} = [l..r]
u>v,v≤r<u,min[l..r]=minl,max[l..r]=[l..r]里的点到
m
+
1
m+1
m+1的最大值。
1可以直接算。
2、3、4维护前缀和就行了。
Code
# include<cstdio>
# include<cstring>
# define ll long long
# define fo(i, x, y) for(ll i = x; i <= y; i ++)
# define fd(i, x, y) for(ll i = x; i >= y; i --)
# define min(a, b) ((a) < (b) ? (a) : (b))
# define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
const ll N = 500005, mo = 1e9 + 7;
ll n, a[N], u[N], v[N], s1[N], s2[N], s3[N];
ll ans;
inline void read(int &sum)
{
char ch = getchar();
int tf = 0;
sum = 0;
while((ch < '0' || ch > '9') && (ch != '-'))
ch = getchar();
tf = ((ch == '-') && (ch = getchar()));
while(ch >= '0' && ch <= '9')
sum = sum * 10 + (ch - 48),ch = getchar();
(tf) && (sum = -sum);
}
void dg(ll x, ll y)
{
if(x > y)
return;
if(x == y)
{
ans = (ans + a[x] * a[x] % mo) % mo;
return;
}
ll m = (x + y) / 2;
dg(x, m); dg(m + 1, y);
u[0] = v[0] = 1;
u[1] = v[1] = m + 1;
s1[m] = s2[m] = s3[m] = 0;
s1[m + 1] = a[m + 1];
s2[m + 1] = a[m + 1];
s3[m + 1] = a[m + 1] * a[m + 1];
fo(i, m + 2, y)
{
if(a[i] < a[u[u[0]]])
u[++ u[0]] = i;
if(a[i] > a[v[v[0]]])
v[++ v[0]] = i;
s1[i] = (s1[i - 1] + a[u[u[0]]]) % mo;
s2[i] = (s2[i - 1] + a[v[v[0]]]) % mo;
s3[i] = (s3[i - 1] + a[u[u[0]]] * a[v[v[0]]]) % mo;
}
ll min = 1e9,max = -1e9,l = 1,r = 1;
ll sum = ans;
fd(i, m, x)
{
min = min(min, a[i]);
max = max(max, a[i]);
while(l <= u[0] && min <= a[u[l]])
l++;
while(r <= v[0] && max >= a[v[r]])
r++;
ll l1 = (l > u[0]) ? y : (u[l] - 1),r1 = (r > v[0]) ? y : (v[r] - 1);
if(min(l1,r1) > m)
ans += (min(l1, r1) - m) * min % mo * max % mo;
if(max(l1, r1) < y)
ans += s3[y] - s3[max(l1, r1)];
if(l1 <= r1)
ans += (s1[r1] - s1[l1]) * max % mo;
else
ans += min * (s2[l1] - s2[r1]) % mo;
ans = (ans % mo + mo) % mo;
}
}
int main()
{
freopen("seq.in", "r", stdin);
freopen("seq.out", "w", stdout);
read(a[i]);
fo(i, 1, n)
read(a[i]);
dg(1, n);
cout << ans;
}
3.带权排序
Description
[外链图片转存中…(img-s7gihznN-1562400240227)]
Solutions
E
[
∑
i
=
1
n
S
i
P
i
]
=
∑
i
=
1
n
S
i
E
[
p
i
]
E\left[\sum\limits^{n}_{i=1}S_iP_i\right]=\sum\limits^{n}_{i=1}\small{S_i}\large E\left[p_i\right]
E[i=1∑nSiPi]=i=1∑nSiE[pi]
考虑怎么求出
E
[
p
i
]
E[p_i]
E[pi]
E
[
p
i
]
=
∑
j
<
i
p
(
a
j
≤
a
i
)
+
∑
j
>
i
p
(
a
j
<
a
i
)
E[p_i]=\sum_{j<i}p(a_j \leq a_i)+\sum_{j>i}p(a_j<a_i)
E[pi]=∑j<ip(aj≤ai)+∑j>ip(aj<ai)
注意到
a
i
a_i
ai的取值是整数,不妨先考虑
∑
j
<
i
p
(
a
j
≤
a
i
)
\sum_{j<i}p(a_j \leq a_i)
∑j<ip(aj≤ai)的贡献。我们从左往右,对于
a
j
a_j
aj,取值为
[
l
j
,
r
i
]
[l_j,r_i]
[lj,ri],考虑当
a
i
=
x
,
i
>
j
a_i=x,i>j
ai=x,i>j时
j
j
j对
i
i
i的贡献,相当于要统计中有
[
l
j
,
r
i
]
[l_j,r_i]
[lj,ri]多少
≤
\leq
≤?的数,分情况讨论:
- l j ≤ x ≤ r j , p ( a j ≤ a i ) = x − l j + 1 r j − l j \normalsize l_j\leq x\leq r_j,p(a_j\leq a_i)=\Large\frac{x-l_j+1}{r_j-l_j} lj≤x≤rj,p(aj≤ai)=rj−ljx−lj+1
-
x
≥
r
j
,
p
(
a
j
≤
a
j
)
=
1
x\geq r_j,p(a_j\leq a_j)=1
x≥rj,p(aj≤aj)=1
可以发现贡献都可以写成一条直线的形式
维护一棵线段树存储每个?收到的贡献。每次相当于区间加一条直线,可以
O ( 1 ) O(1) O(1)合并, O ( 1 ) O(1) O(1)得到新的区间和。
对于 p ( a j ≤ a j ) p(a_j\leq a_j) p(aj≤aj),直接求出 x ∈ [ l i , r i ] x \in [l_i,r_i] x∈[li,ri]的区间和即可。
对于 j < i j<i j<i的情况类似。
总复杂度 O ( n log n ) O(n\log n) O(nlogn)。
Code
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=1e5+7,mo=1e9+7;
typedef long long ll;
struct node{
ll l,r;
ll sum,s,k,bz;
}t[maxn*100];
ll i,j,k,l,n,m,root,num,da;
ll ans,ni[maxn],ni2;
struct nod{
ll a,l,r;
}a[maxn];
ll qsm(ll x,ll y){
ll z=1;
for(;y;y/=2,x=x*x%mo)if(y&1)z=z*x%mo;
return z;
}
void down(ll x,ll l,ll r){
ll y=t[x].l,z=t[x].r;
if(t[x].s||t[x].k){
if(!y)t[y=t[x].l=++num]=(node){0,0,0,0,0,0};
if(!z)t[z=t[x].r=++num]=(node){0,0,0,0,0,0};
ll mid=(l+r)/2,s=t[x].s,k=t[x].k,ss=(s+(mid-l)*t[x].k%mo)%mo;
(t[y].sum+=(s+ss)%mo*(mid-l+1)%mo*ni2%mo)%=mo;(t[y].s+=s)%=mo,(t[y].k+=k)%=mo;
s=(ss+k)%mo;ss=(t[x].s+(r-l)*k%mo)%mo;
(t[z].sum+=(s+ss)%mo*(r-mid)%mo*ni2%mo)%=mo;(t[z].s+=s)%=mo,(t[z].k+=k)%=mo;
t[x].s=t[x].k=0;
}
if(t[x].bz){
if(!y)t[y=t[x].l=++num]=(node){0,0,0,0,0,0};
if(!z)t[z=t[x].r=++num]=(node){0,0,0,0,0,0};
ll mid=(l+r)/2;
(t[y].sum+=(mid-l+1)*t[x].bz%mo)%=mo;(t[z].sum+=(r-mid)*t[x].bz%mo)%=mo;
(t[y].bz+=t[x].bz)%=mo;(t[z].bz+=t[x].bz)%=mo;
t[x].bz=0;
}
}
void change1(ll &x,ll l,ll r,ll y,ll z,ll s,ll k){
if(y>z)return;
if(!x)t[x=++num]=(node){0,0,0,0,0,0};
if(l==y&&r==z){
(t[x].sum+=(s*2%mo+(r-l)*k%mo)%mo*(r-l+1)%mo*ni2%mo)%=mo;
(t[x].s+=s)%=mo,(t[x].k+=k)%=mo;
return;
}
down(x,l,r);
ll mid=(l+r)/2;
if(z<=mid)change1(t[x].l,l,mid,y,z,s,k);
else if(y>mid)change1(t[x].r,mid+1,r,y,z,s,k);
else{
change1(t[x].l,l,mid,y,mid,s,k);
change1(t[x].r,mid+1,r,mid+1,z,(s+(mid-y+1)*k%mo)%mo,k);
}
t[x].sum=0;if(t[x].l)t[x].sum=t[t[x].l].sum;
if(t[x].r)(t[x].sum+=t[t[x].r].sum)%=mo;
}
void change2(ll &x,ll l,ll r,ll y,ll z,ll o){
if(y>z)return;
if(!x)t[x=++num]=(node){0,0,0,0,0,0};
if(l==y&&r==z){
(t[x].sum+=(r-l+1)*o%mo)%=mo;
(t[x].bz+=o)%=mo;
return;
}
down(x,l,r);
ll mid=(l+r)/2;
if(z<=mid)change2(t[x].l,l,mid,y,z,o);
else if(y>mid)change2(t[x].r,mid+1,r,y,z,o);
else{
change2(t[x].l,l,mid,y,mid,o);
change2(t[x].r,mid+1,r,mid+1,z,o);
}
t[x].sum=0;if(t[x].l)t[x].sum=t[t[x].l].sum;
if(t[x].r)(t[x].sum+=t[t[x].r].sum)%=mo;
}
ll find(ll x,ll l,ll r,ll y,ll z){
if(!x)return 0;
if(l==y&&r==z)return t[x].sum;
down(x,l,r);
ll mid=(l+r)/2;
if(z<=mid)return find(t[x].l,l,mid,y,z);
else if(y>mid)return find(t[x].r,mid+1,r,y,z);
else return(find(t[x].l,l,mid,y,mid)+find(t[x].r,mid+1,r,mid+1,z))%mo;
}
int main(){
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
scanf("%d",&n);ni2=(mo+1)/2;
fo(i,1,n)scanf("%d%d%d",&a[i].a,&a[i].l,&a[i].r),da=max(da,a[i].r),ans=(ans+a[i].a)%mo,ni[i]=qsm(a[i].r-a[i].l+1,mo-2);
fo(i,1,n){
(ans+=a[i].a*find(root,0,da,a[i].l,a[i].r)%mo*ni[i]%mo)%=mo;
change1(root,0,da,a[i].l,a[i].r,ni[i],ni[i]);
change2(root,0,da,a[i].r+1,da,1);
}
root=num=0;t[1]=(node){0,0,0,0,0,0};
fod(i,n,1){
(ans+=a[i].a*find(root,0,da,a[i].l,a[i].r)%mo*ni[i]%mo)%=mo;
change1(root,0,da,a[i].l+1,a[i].r,ni[i],ni[i]);
change2(root,0,da,a[i].r+1,da,1);
}
printf("%lld\n",ans);
}