Noip模拟赛day2:解题报告

本文探讨了状压动态规划解决队伍排列问题,及分治策略处理序列最大最小值乘积问题,还介绍了带权排序的期望计算方法,通过线段树优化复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.队伍统计

Description

现在有n个人要排成一列,编号为1→n1\to n1n 。但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有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=xv=yv=yv=y

Output

输出包括一行表示合法的排列数。

Data Constraint

对于30%30\%30%的数据,n≤10n\leq10n10
对于60%60\%60%的数据,n≤15n\leq15n15
对应100%100\%100%的数据,n,k≤20,m≤n∗(n−1)n,k\leq20,m\leq n*(n-1)n,k20,mn(n1),保证矛盾关系不重复。

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(2NNK)

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=1nr=lnf(l,r)g(l,r))mod(1e9+7)

Input

首先输入n。
接下来输入n个数,描述序列 A。

Output

输出一行一个整数代表答案。

Data Constraint

对于30%30\%30%的数据,n≤5000n\leq5000n5000
对于60%60\%60%的数据,n≤50000n\leq50000n50000
对于100%100\%100%的数据,n≤500000,0≤A[i]≤109n\leq500000,0\leq A[i]\leq10^9n500000,0A[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&lt;min(u,v),min[l..r]=minl,min[l..r]=minrr &lt; 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]rmax(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&lt;v,min[l..r]=[l..r]u \leq v, u\leq r &lt; v,min_{[l..r]} = [l..r]uv,ur<v,min[l..r]=[l..r]里的点到m+1m+1m+1的最小值,max[l..r]=minrmax_{[l..r]} = min_rmax[l..r]=minr
4.u&gt;v,v≤r&lt;u,min[l..r]=minl,max[l..r]=[l..r]u &gt;v, v\leq r &lt; u,min_{[l..r]} = min_l,max_{[l..r]} = [l..r]u>v,vr<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=1nSiPi]=i=1nSiE[pi]
考虑怎么求出 E[pi]E[p_i]E[pi]
E[pi]=∑j&lt;ip(aj≤ai)+∑j&gt;ip(aj&lt;ai)E[p_i]=\sum_{j&lt;i}p(a_j \leq a_i)+\sum_{j&gt;i}p(a_j&lt;a_i)E[pi]=j<ip(ajai)+j>ip(aj<ai)
注意到aia_iai的取值是整数,不妨先考虑∑j&lt;ip(aj≤ai)\sum_{j&lt;i}p(a_j \leq a_i)j<ip(ajai)的贡献。我们从左往右,对于aja_jaj,取值为[lj,ri][l_j,r_i][lj,ri],考虑当ai=x,i&gt;ja_i=x,i&gt;jai=x,i>jjjjiii的贡献,相当于要统计中有[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}ljxrj,p(ajai)=rjljxlj+1
  • x≥rj,p(aj≤aj)=1x\geq r_j,p(a_j\leq a_j)=1xrj,p(ajaj)=1
    可以发现贡献都可以写成一条直线的形式
    维护一棵线段树存储每个?收到的贡献。每次相当于区间加一条直线,可以
    O(1)O(1)O(1)合并,O(1)O(1)O(1)得到新的区间和。
    对于p(aj≤aj)p(a_j\leq a_j)p(ajaj),直接求出x∈[li,ri]x \in [l_i,r_i]x[li,ri]的区间和即可。
    对于j&lt;ij&lt;ij<i的情况类似。
    总复杂度O(nlog⁡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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值