1.队伍统计
Description
现在有n个人要排成一列,编号为1→n1\to n1→n 。但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有m条矛盾关系(u,v),表示编号为u的人想要排在编号为v的人前面。要使得队伍和谐,最多不能违背k条矛盾关系(即不能有超过k条矛盾关系(u,v)(u,v)(u,v),满足最后v排在了u前面)。问有多少合法的排列。答案对109+710^9+7109+7取模。
Input
第一行包括三个整数n,m,kn,m,kn,m,k。
接下来mmm行,每行两个整数u,vu,vu,v,描述一个矛盾关系(u,v)(u,v)(u,v)。
保证不存在两对矛盾关系(u,v),(x,y)(u,v),(x,y)(u,v),(x,y),使得u=xu=xu=x且v=yv=yv=y 。
Output
输出包括一行表示合法的排列数。
Data Constraint
对于30%30\%30%的数据,n≤10n\leq10n≤10
对于60%60\%60%的数据,n≤15n\leq15n≤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
看到数据范围,显然就是状压DPDPDP了。
设 F[s][i]F[s][i]F[s][i] 表示已经选了的人的集合为sss 、已经违背了iii 条矛盾关系的合法排列数。
转移时枚举将要选的人,处理出会产生的矛盾关系即可。
那么如何快速处理出将会产生的矛盾关系呢?
考虑预处理出 a[x]a[x]a[x]表示排在xxx以前会有矛盾的人的集合,
那么与 s&s\&s& 的值二进制的111 的个数就是所求的数量了。
时间复杂度为O(2N∗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
给定一个长度为nnn的序列AAA。定义f(l,r)=max(al,al+1,...,ar)f(l,r) = max(a_l,a_{l+1},...,a_r)f(l,r)=max(al,al+1,...,ar),
g(l,r)=min(al,al+1,...,ar)g(l,r)=min(a_l,a_{l+1},...,a_r)g(l,r)=min(al,al+1,...,ar),希望你求出:
(∑l=1n∑r=lnf(l,r)∗g(l,r))mod(1e9+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≤5000n\leq5000n≤5000
对于60%60\%60%的数据,n≤50000n\leq50000n≤50000
对于100%100\%100%的数据,n≤500000,0≤A[i]≤109n\leq500000,0\leq A[i]\leq10^9n≤500000,0≤A[i]≤109
Solutions
这种题马上想到的就是分治。
对于区间[x..y][x..y][x..y],将它分成三部分:
m=x+y2m = \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.左右端点在mmm的两旁。
前两个递归处理,考虑第三个怎么求,这是分治的常规套路。
先考虑区间[m+1..y][m + 1..y][m+1..y](右区间),以m+1m+1m+1为左端点,从左往右枚举右端点,minminmin值会不断变小,maxmaxmax值会不断变大,将变化的地方存下来,分别放进两个数组里,设为a,ba,ba,b。
现在还要考虑区间[x..m][x..m][x..m](左区间),从mmm出发,从右往左枚举左端点l,记录下minminmin值和maxmaxmax值,设为minl\large min_lminl。
最后需要将两个区间合并。
在aaa数组里找到代表的值第一个小于minlmin_lminl的位置uuu(从左往右看),
在bbb数组里找到代表的值第一个大于maxlmax_lmaxl的位置vvv(从左往右看)。
这个可以二分。
由于minlmin_lminl不断缩小,maxrmax_rmaxr不断变大,也可以直接维护个指针。
右端点r的取法接下来有四种情况:
1.r<min(u,v),min[l..r]=minl,min[l..r]=minrr < min(u, v),min_{[l..r]} = min_l,min_{[l..r]} = min_rr<min(u,v),min[l..r]=minl,min[l..r]=minr
2.r≥max(u,v),min[l..r]=[l..r]r \geq max(u, v), min_{[l..r]} = [l..r]r≥max(u,v),min[l..r]=[l..r]里的点到m+1m+1m+1的最小值,max[l..r]=[l..r]max_[l..r] = [l..r]max[l..r]=[l..r]里的点到m+1m+1m+1的最大值。
3.u≤v,u≤r<v,min[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+1m+1m+1的最小值,max[l..r]=minrmax_{[l..r]} = min_rmax[l..r]=minr。
4.u>v,v≤r<u,min[l..r]=minl,max[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+1m+1m+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=1nSiPi]=∑i=1nSiE[pi]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[pi]E[p_i]E[pi]
E[pi]=∑j<ip(aj≤ai)+∑j>ip(aj<ai)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)
注意到aia_iai的取值是整数,不妨先考虑∑j<ip(aj≤ai)\sum_{j<i}p(a_j \leq a_i)∑j<ip(aj≤ai)的贡献。我们从左往右,对于aja_jaj,取值为[lj,ri][l_j,r_i][lj,ri],考虑当ai=x,i>ja_i=x,i>jai=x,i>j时jjj对iii的贡献,相当于要统计中有[lj,ri][l_j,r_i][lj,ri]多少≤\leq≤?的数,分情况讨论:
- lj≤x≤rj,p(aj≤ai)=x−lj+1rj−lj\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≥rj,p(aj≤aj)=1x\geq r_j,p(a_j\leq a_j)=1x≥rj,p(aj≤aj)=1
可以发现贡献都可以写成一条直线的形式
维护一棵线段树存储每个?收到的贡献。每次相当于区间加一条直线,可以
O(1)O(1)O(1)合并,O(1)O(1)O(1)得到新的区间和。
对于p(aj≤aj)p(a_j\leq a_j)p(aj≤aj),直接求出x∈[li,ri]x \in [l_i,r_i]x∈[li,ri]的区间和即可。
对于j<ij<ij<i的情况类似。
总复杂度O(nlogn)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);
}