Noip模拟赛day2:解题报告

1.队伍统计

Description

现在有n个人要排成一列,编号为 1 → n 1\to n 1n 。但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有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 n10
对于 60 % 60\% 60%的数据, n ≤ 15 n\leq15 n15
对应 100 % 100\% 100%的数据, n , k ≤ 20 , m ≤ n ∗ ( n − 1 ) n,k\leq20,m\leq n*(n-1) n,k20,mn(n1),保证矛盾关系不重复。

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(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

给定一个长度为 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=1nr=lnf(l,r)g(l,r))mod(1e9+7)

Input

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

Output

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

Data Constraint

对于 30 % 30\% 30%的数据, n ≤ 5000 n\leq5000 n5000
对于 60 % 60\% 60%的数据, n ≤ 50000 n\leq50000 n50000
对于 100 % 100\% 100%的数据, n ≤ 500000 , 0 ≤ A [ i ] ≤ 1 0 9 n\leq500000,0\leq A[i]\leq10^9 n500000,0A[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 &lt; m i n ( u , v ) , m i n [ l . . r ] = m i n l , m i n [ l . . r ] = m i n r r &lt; 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] rmax(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 &lt; v , m i n [ 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 + 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 &gt; v , v ≤ r &lt; u , m i n [ l . . r ] = m i n l , m a x [ 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 + 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=1nSiPi]=i=1nSiE[pi]
考虑怎么求出 E [ p i ] E[p_i] E[pi]
E [ p i ] = ∑ j &lt; i p ( a j ≤ a i ) + ∑ j &gt; i p ( a j &lt; a i ) 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)
注意到 a i a_i ai的取值是整数,不妨先考虑 ∑ j &lt; i p ( a j ≤ a i ) \sum_{j&lt;i}p(a_j \leq a_i) j<ip(ajai)的贡献。我们从左往右,对于 a j a_j aj,取值为 [ l j , r i ] [l_j,r_i] [lj,ri],考虑当 a i = x , i &gt; j a_i=x,i&gt;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} ljxrj,p(ajai)=rjljxlj+1
  • x ≥ r j , p ( a j ≤ a j ) = 1 x\geq r_j,p(a_j\leq a_j)=1 xrj,p(ajaj)=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(ajaj),直接求出 x ∈ [ l i , r i ] x \in [l_i,r_i] x[li,ri]的区间和即可。
    对于 j &lt; i j&lt;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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值