NOIP2018模拟赛总结(2018.10.24-2018.11.5)

2018.10.24

据说是九校联考的题(虽然不知道哪九校),老师拿来考了,考得还算不错吧…

T1 Backpack

题目大意: 给定 n ( ≤ 1 0 6 ) n(\le 10^6) n(106)个物品,每个物品有体积 a ( ≤ 100 ) a(\le 100) a(100)和价格 b ( ≤ 100 ) b(\le 100) b(100),同种物品可以取无穷多个,问总重量不超过 m ( ≤ 1 0 16 ) m(\le 10^{16}) m(1016)的情况下能获得的最大价值。
做法: 本题需要用到完全背包+贪心。
乍一看就是裸的完全背包,但由于容量太大,就算对于体积去重,并进行二进制压缩也只能进行到 m ≤ 1 0 5 m\le 10^5 m105次方的级别。这可怎么办呢?注意到单个物品体积很小,而容量很大,那么这些容量的很大一个部分,一定是取性价比最高的那一个。因此我们贪心地取性价比最高的元素,取到剩下的 m ≤ 1 0 5 m\le 10^5 m105时再DP)即可)。
(不过出题人貌似有严格证明,有待进一步理解)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int n,tot;
ll m,mx[120]={0},f[100010]={0};
ll a[2010],b[2010],ans;

int main()
{
	scanf("%d%lld",&n,&m);
	ll mxa=1,mxb=0;
	for(int i=1;i<=n;i++)
	{
		int a;ll b;
		scanf("%d%lld",&a,&b);
		mx[a]=max(mx[a],b);
		if (mxa*b>mxb*a)
			mxa=a,mxb=b;
	}
	
	ll l=0,r=m/mxa;
	while(l<r)
	{
		ll mid=(l+r)>>1;
		if ((mid+1ll)*mxa+100000>m) r=mid;
		else l=mid+1; 
	}
	m-=l*mxa;
	ans=l*mxb;
	
	tot=0;
	for(int i=1;i<=100;i++)
		if (mx[i])
		{
			ll nowa=i,nowb=mx[i];
			while(nowa<=m)
			{
				a[++tot]=nowa;
				b[tot]=nowb;
				nowa<<=1,nowb<<=1;
			}
		}
	for(int i=1;i<=tot;i++)
		for(int j=m;j>=a[i];j--)
			f[j]=max(f[j],f[j-a[i]]+b[i]);
	printf("%lld",f[m]+ans);
	
	return 0;
}
T2 Sort

题目大意: 给定一个长为 2 n ( n ≤ 5 × 1 0 5 ) 2n(n\le 5\times 10^5) 2n(n5×105)的不递减的序列,要求维护区间加同一个值,并对一个长为偶数的区间询问:定义一个长为偶数的区间的划分为,将这个区间的元素分为两个部分,其中对这个区间每一个前缀,在 A A A部分中的元素个数都大于等于在 B B B部分中的元素个数,令 A i , B i A_i,B_i Ai,Bi为从左到右第 i i i A A A B B B部分中的元素,问 ∑ ( B i − A i ) \sum (B_i-A_i) (BiAi)的最大值和最小值,以及合法划分的方案数。保证序列任何时候都是不递减的。
做法: 本题需要用到线段树+卡特兰数。
方案数显然就是长为 2 k 2k 2k的合法括号序列数量,也就是卡特兰数 1 k + 1 ⋅ C 2 k k \frac{1}{k+1}\cdot C_{2k}^k k+11C2kk。而差值最大,显然应该是选择的 B i B_i Bi之和越大差值越大,而序列不递减,因此 B B B选择右边的一半是最优的,线段树维护区间和即可。差值最小的话,一定是选一个 A A A,选一个 B B B,再选一个 A A A…这样的形式,用线段树分别维护区间奇偶位置的元素和即可,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int n,m;
ll fac[1000010],inv[1000010],invfac[1000010];
ll a[1000010],sum[4000010],pk[4000010][2];
ll tag[4000010];

void update(int no,int l,int r,ll d)
{
	ll p0=(r-l+2)>>1,p1=(r-l+1)>>1;
	tag[no]+=d;
	sum[no]+=(ll)(r-l+1)*d;
	pk[no][0]+=p0*d;
	pk[no][1]+=p1*d;
}

void pushdown(int no,int l,int r)
{
	int mid=(l+r)>>1;
	if (tag[no]!=0)
	{
		update(no<<1,l,mid,tag[no]);
		update(no<<1|1,mid+1,r,tag[no]);
		tag[no]=0;
	}
}

void pushup(int no,int l,int r)
{
	int mid=(l+r)>>1;
	sum[no]=sum[no<<1]+sum[no<<1|1];
	pk[no][0]=pk[no<<1][0];
	pk[no][1]=pk[no<<1][1];
	bool f=(mid+1-l)%2;
	pk[no][0]+=pk[no<<1|1][f];
	pk[no][1]+=pk[no<<1|1][!f];
}

void buildtree(int no,int l,int r)
{
	tag[no]=0;
	if (l==r)
	{
		sum[no]=pk[no][0]=a[l];
		pk[no][1]=0;
		return;
	}
	int mid=(l+r)>>1;
	buildtree(no<<1,l,mid);
	buildtree(no<<1|1,mid+1,r);
	pushup(no,l,r);
}

void modify(int no,int l,int r,int s,int t,ll d)
{
	if (l>=s&&r<=t)
	{
		update(no,l,r,d);
		return;
	}
	int mid=(l+r)>>1;
	pushdown(no,l,r);
	if (s<=mid) modify(no<<1,l,mid,s,t,d);
	if (t>mid) modify(no<<1|1,mid+1,r,s,t,d);
	pushup(no,l,r);
}

ll querysum(int no,int l,int r,int s,int t)
{
	if (l>=s&&r<=t) return sum[no];
	int mid=(l+r)>>1;
	ll ret=0;
	pushdown(no,l,r);
	if (s<=mid) ret+=querysum(no<<1,l,mid,s,t);
	if (t>mid) ret+=querysum(no<<1|1,mid+1,r,s,t);
	return ret;
}

ll querypk(int no,int l,int r,int s,int t)
{
	if (l>=s&&r<=t)
	{
		bool f=(l-s)%2;
		return pk[no][!f]-pk[no][f];
	}
	int mid=(l+r)>>1;
	ll ret=0;
	pushdown(no,l,r);
	if (s<=mid) ret+=querypk(no<<1,l,mid,s,t);
	if (t>mid) ret+=querypk(no<<1|1,mid+1,r,s,t);
	return ret;
}

int main()
{
	scanf("%d%d",&n,&m);
	n<<=1;
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	
	fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
	for(ll i=2;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		invfac[i]=invfac[i-1]*inv[i]%mod;
	}
	
	buildtree(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int op,x,y;
		ll d;
		scanf("%d",&op);
		if (op==0)
		{
			scanf("%d%d%lld",&x,&y,&d);
			modify(1,1,n,x,y,d);
		}
		else
		{
			scanf("%d%d",&x,&y);
			int mid=(x+y)>>1,len=(y-x+1)>>1;
			printf("%lld ",(querysum(1,1,n,mid+1,y)-querysum(1,1,n,x,mid))%mod);
			printf("%lld ",querypk(1,1,n,x,y)%mod);
			printf("%lld\n",fac[len<<1]%mod*invfac[len]%mod*invfac[len+1]%mod);
		}
	}
	
	return 0;
}
T3 Digit

题目大意: 求位数在 l ( ≤ 1 0 18 ) l(\le 10^{18}) l(1018) r ( ≤ 1 0 18 ) r(\le 10^{18}) r(1018)位之间的 k ( ≤ 1 0 18 ) k(\le 10^{18}) k(1018)进制数中,数位和是 5 5 5的倍数,或是 4 4 4的倍数,或是 6 6 6的倍数的数的数量。
做法: 本题需要用到容斥+矩阵快速幂+等比二分。
题目中这个形式显然容斥处理一下,规约成求数位和为某个数 p p p的倍数的数有多少个。差分转化一下,变成求位数 ≤ x \le x x的满足要求的数有多少个。令 f ( i , j ) f(i,j) f(i,j) i i i位数中,数位和对 p p p取模余数为 j j j的数的数量,有状态转移方程:
f ( i , j ) = ∑ n x t = 0 p − 1 c n t ( n x t ) ⋅ f ( i − 1 , ( j − n x t ) % p ) f(i,j)=\sum_{nxt=0}^{p-1}cnt(nxt)\cdot f(i-1,(j-nxt)\%p) f(i,j)=nxt=0p1cnt(nxt)f(i1,(jnxt)%p)
其中 c n t ( n x t ) cnt(nxt) cnt(nxt) 0 0 0 ~ k − 1 k-1 k1中对 p p p取模余数为 n x t nxt nxt的数的数量,这个可以 O ( p ) O(p) O(p)处理出来。看到这个递推式的形式,想到用矩阵快速幂优化DP,但我们最后要求的是 ∑ i = 1 x f ( i , 0 ) \sum_{i=1}^x f(i,0) i=1xf(i,0),所以还要等比二分。弄完了上面一堆操作,我们达到了 O ( l c m ( 5 , 4 , 6 ) 3 log ⁡ r ) O(lcm(5,4,6)^3\log r) O(lcm(5,4,6)3logr)的复杂度。
然而还是会爆, l = r = k = 1 0 18 l=r=k=10^{18} l=r=k=1018时开O2都得跑8.5s。观察上面写出的转移矩阵,发现这个矩阵是一个循环矩阵(指上一行向右移一位等于下一行的矩阵),我们发现循环矩阵和循环矩阵相加或相乘还是循环矩阵,所以我们只需要维护矩阵的第一行就行了,那么矩阵乘法的时间复杂度就降低为 O ( l c m ( 5 , 4 , 6 ) 2 ) O(lcm(5,4,6)^2) O(lcm(5,4,6)2),于是 O ( l c m ( 5 , 4 , 6 ) 2 log ⁡ n ) O(lcm(5,4,6)^2\log n) O(lcm(5,4,6)2logn)的时间复杂度能轻松地通过此题。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int m,tot;
ll l,r,k,ans,path[1010];
struct matrix
{
	ll s[100];
}A[70],E,S,Ans;
ll F[100];

matrix Mult(matrix A,matrix B)
{
	memset(S.s,0,sizeof(S.s));
	for(int i=0;i<m;i++)
		for(int j=0;j<m;j++)
			S.s[i]=(S.s[i]+A.s[j]*B.s[(m+i-j)%m])%mod;
	return S;
}

matrix Add(matrix A,matrix B)
{
	for(int i=0;i<m;i++)
		S.s[i]=A.s[i]+B.s[i];
	return S;
}

matrix Power(ll b)
{
	S=E;
	int i=0;
	while(b)
	{
		if (b&1) S=Mult(S,A[i]);
		i++;b>>=1;
	}
	return S;
}

void calc_A()
{
	memset(E.s,0,sizeof(E.s));
	E.s[0]=1;
	for(int i=0;i<m;i++)
	{
		F[i]=(k/(ll)m)%mod;
		if (i<=k%(ll)m) F[i]=(F[i]+1ll)%mod;
	}
	for(int i=0;i<m;i++)
		A[0].s[i]=F[(m-i)%m];
	for(int i=1;(1ll<<i)<=r;i++)
		A[i]=Mult(A[i-1],A[i-1]);
	F[0]=(F[0]-1ll+mod)%mod;
}

matrix calc(ll x)
{
	Ans=E;
	tot=0;
	while(x)
	{
		path[++tot]=x;
		if (x%2ll==0) x>>=1;
		else x--;
	}
	for(int i=tot-1;i>=1;i--)
	{
		if (path[i]==path[i+1]+1)
			Ans=Add(Mult(Ans,A[0]),E);
		else Ans=Mult(Ans,Add(Power(path[i]>>1),E));
	}
	return Ans;
}

void solve(int x,ll type)
{
	ll ret=0;
	
	m=x;
	calc_A();
	
	calc(r);
	for(int i=0;i<m;i++)
		ret=(ret+Ans.s[i]*F[i])%mod;
	
	if (l>1)
	{
		calc(l-1);
		for(int i=0;i<m;i++)
			ret=((ret-Ans.s[i]*F[i])%mod+mod)%mod;
	}
	
	ans=(ans+type*ret)%mod;
}

int main()
{
	scanf("%lld%lld%lld",&l,&r,&k);
	k--;
	
	ans=0;
	solve(5,1ll);
	solve(4,1ll);
	solve(6,1ll);
	solve(20,mod-1ll);
	solve(12,mod-1ll);
	solve(30,mod-1ll);
	solve(60,1ll);
	printf("%lld",ans);
	
	return 0;
}

2018.10.25

T1 Median

题目大意: 给定两个长为 n n n的非严格单调递增的序列 A , B A,B A,B m m m个操作,要求维护单点修改(不改变非严格单调递增的性质),以及询问 A A A的区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] B B B的区间 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]拼起来之后的中位数,保证区间拼起来后长度为奇数。 n ≤ 5 × 1 0 5 , m ≤ 1 0 6 n\le 5\times 10^5,m\le 10^6 n5×105,m106
做法: 本题需要用到trie/权值线段树+二分。
很快能想到二分套lower_bound之类奇奇怪怪的两个 log ⁡ \log log算法,然而这题数据范围明示了你需要用一个 log ⁡ \log log的算法,这怎么办呢?
我们发现上述算法中最大的问题就是两边的二分不同步,所以要使两边二分同步,只有一个方法:权值线段树/trie,因此我们直接对两个序列分别开权值线段树,两边同时二分即可。
(结果我却没想出来,我好菜啊)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,tot=0,rt[2]={0},a[2][500010];
int ch[30000010][2]={0},siz[30000010]={0};

int read()
{
    char c = getchar(); int x = 0; for(;c < '0' || c > '9'; c = getchar());
    for(;c >= '0' && c <= '9'; c = getchar()) x = x * 10 - '0' + c; return x;
}

void insert(int &v,int l,int r,int x,int d)
{
    if (!v) v=++tot;
    if (l==r) {siz[v]+=d;return;}
    int mid=(l+r)>>1;
    if (x<=mid) insert(ch[v][0],l,mid,x,d);
    else insert(ch[v][1],mid+1,r,x,d);
    siz[v]=siz[ch[v][0]]+siz[ch[v][1]];
}

int query(int v1,int v2,int l,int r,int x,int y,int z,int d,int k,int sum1,int sum2)
{
    if (l==r) return l;
    int mid=(l+r)>>1;
    if (max(min(y,sum1+siz[ch[v1][0]])-x+1,0)+max(min(d,sum2+siz[ch[v2][0]])-z+1,0)>=k)
        return query(ch[v1][0],ch[v2][0],l,mid,x,y,z,d,k,sum1,sum2);
    else return query(ch[v1][1],ch[v2][1],mid+1,r,x,y,z,d,k,sum1+siz[ch[v1][0]],sum2+siz[ch[v2][0]]);
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        a[0][i]=read();
        insert(rt[0],0,1000000000,a[0][i],1);
    }
    for(int i=1;i<=n;i++)
    {
        a[1][i]=read();
        insert(rt[1],0,1000000000,a[1][i],1);
    }
    
    for(int i=1;i<=m;i++)
    {
        int op,x,y,z,d;    
        op=read();
        if (op==1)
        {
            x=read(),y=read(),z=read();
            insert(rt[x],0,1000000000,a[x][y],-1);
            a[x][y]=z;
            insert(rt[x],0,1000000000,a[x][y],1);
        }
        else
        {
            x=read(),y=read(),z=read(),d=read();
            printf("%d\n",query(rt[0],rt[1],0,1000000000,x,y,z,d,(y+d-x-z+3)>>1,0,0));
        }
    }
    
    return 0;
}                                                   
T2 Min

题目大意: 给定一个函数 F ( x ) = A x 3 + B x 2 + C x + D F(x)=Ax^3+Bx^2+Cx+D F(x)=Ax3+Bx2+Cx+D,其中 A , B , C , D A,B,C,D A,B,C,D给定,再给定一个长为 n n n的序列 a a a,定义一个区间 [ l , r ] [l,r] [l,r]的价值为 f ( min ⁡ i = l r a i ) f(\min_{i=l}^ra_i) f(mini=lrai),要求把序列分成任意多段,使得这些段的价值之和最大。 n ≤ 2 × 1 0 5 n\le 2\times 10^5 n2×105
做法: 本题需要用到DP+单调栈+堆。
首先我们先写出最好想的DP方程:
f ( i ) = max ⁡ { f ( j − 1 ) + F ( min ⁡ k = j i a k ) } f(i)=\max\{f(j-1)+F(\min_{k=j}^ia_k)\} f(i)=max{f(j1)+F(mink=jiak)}
预处理 F ( min ⁡ k = j i a k ) F(\min_{k=j}^ia_k) F(mink=jiak),可以做到 O ( n 2 ) O(n^2) O(n2)
然而显然过不了。我们发现当 i i i固定时, g j = min ⁡ k = j i a k g_j=\min_{k=j}^ia_k gj=mink=jiak是一个非严格单调递减的序列,于是可以根据 g j g_j gj分成一段一段,每一段中 g j g_j gj相等,于是我们只需要知道这一段中最大的 f ( j − 1 ) f(j-1) f(j1),就能得到这些点的最大转移了。而在 i i i右移时,显然就是维护一个单调栈了,于是用堆来存储每一段中转移的最大值,转移的时候取堆顶即可(好像也可以直接在单调栈上维护?),因为单调栈中一个元素最多入队一次,出队一次,因此在堆中插入/删除元素最多只有 2 n 2n 2n次,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const ll inf=10000000000001ll;
int n,top;
ll a[200010],f[200010],A,B,C,D;
ll st[200010],mxf[200010];
struct Heap
{
    priority_queue<ll> a,b;
    void insert(ll x) {a.push(x);}
    void Delete(ll x) {b.push(x);}
    ll top()
    {
        while(!b.empty()&&b.top()==a.top())
            a.pop(),b.pop();
        return a.top();
    }
}ans;

ll F(ll x)
{
    return A*x*x*x+B*x*x+C*x+D;
}

int main()
{
    scanf("%d%lld%lld%lld%lld",&n,&A,&B,&C,&D);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    
    f[0]=0;top=0;
    for(int i=1;i<=n;i++)
    {
        ll nowmx=f[i-1],nowst=a[i];
        while(top&&nowst<=st[top])
        {
            nowmx=max(nowmx,mxf[top]);
            ans.Delete(mxf[top]+F(st[top]));
            top--;
        }
        st[++top]=nowst,mxf[top]=nowmx;
        ans.insert(mxf[top]+F(st[top]));
        f[i]=ans.top();
    }
    printf("%lld",f[n]);
    
    return 0;
}
T3 Max

题目大意: n n n个排成一排的魔法阵,有 m m m块魔法石,第 i i i块魔法石有 p i p_i pi的概率,以 y i y_i yi的价值在第 x i x_i xi个魔法阵上出现。魔法阵的价值是在它上面出现的所有魔法石中价值的最小值。要汲取 q q q次能量,每次汲取一个区间 [ l , r ] [l,r] [l,r],保证这些区间互不包含,汲取的结果是 l l l到第 r r r个魔法阵价值的最大值,求 q q q次汲取能得到的结果总和的期望值。 q ≤ n ≤ 1 0 5 , m ≤ 2 × 1 0 5 q\le n\le 10^5,m\le 2\times 10^5 qn105,m2×105
做法: 本题需要用到概率期望+线段树。
挺神的一道题。根据期望可加性,显然可以分开考虑每个询问的期望值再相加,令 a n s ans ans为询问的结果,根据整数概率公式有:
E [ a n s ] = ∑ x = 1 ∞ P ( a n s ≥ x ) E[ans]=\sum_{x=1}^\infty P(ans\ge x) E[ans]=x=1P(ansx)
= ∑ x = 1 ∞ 1 − P ( a n s < x ) =\sum_{x=1}^\infty 1-P(ans<x) =x=11P(ans<x)
a n s < x ans<x ans<x的概率,显然等于区间中每一个魔法阵价值都 < x <x <x的概率,令 v i v_i vi为第 i i i个魔法阵的价值,则有:
E [ a n s ] = ∑ x = 1 ∞ ( 1 − ∏ i = l r P ( v i < x ) ) E[ans]=\sum_{x=1}^\infty (1-\prod_{i=l}^rP(v_i<x)) E[ans]=x=1(1i=lrP(vi<x))
= ∑ x = 1 ∞ ( 1 − ∏ i = l r [ 1 − P ( v i ≥ x ) ] ) =\sum_{x=1}^\infty (1-\prod_{i=l}^r[1-P(v_i\ge x)]) =x=1(1i=lr[1P(vix)])
注意到 x > max ⁡ i = l r v i x>\max_{i=l}^r v_i x>maxi=lrvi的时候,里面的式子为 0 0 0,这就说明期望是有限的,因此我们只需计算 x ≤ max ⁡ i = l r v i x\le\max_{i=l}^r v_i xmaxi=lrvi的情况即可.当然上限弄大一点也不会错,也方便一些后面的处理,因此我们框一个上界 n ≥ max ⁡ i = l r v i n\ge \max_{i=l}^rv_i nmaxi=lrvi,发现那个 1 − . . . 1-... 1...的东西可以拆开,变成:
E [ a n s ] = n − ∑ x = 1 n ∏ i = l r [ 1 − P ( v i ≥ x ) ] E[ans]=n-\sum_{x=1}^n\prod_{i=l}^r[1-P(v_i\ge x)] E[ans]=nx=1ni=lr[1P(vix)]
于是我们只需要维护目前的 ∏ i = l r [ 1 − P ( v i ≥ x ) ] \prod_{i=l}^r[1-P(v_i\ge x)] i=lr[1P(vix)]即可,令这个东西为 Q Q Q
先考虑 P ( v i ≥ x ) P(v_i\ge x) P(vix)怎么求。 v i v_i vi ≥ x \ge x x,就意味着在第 i i i个魔法阵上,价值 < x <x <x的全不出现,并且价值 ≥ x \ge x x的至少有一个出现。这也就是价值 < x <x <x的全不出现的概率,减去这个魔法阵上所有的魔法石都不出现的概率(因为条件可以看成, < x <x <x全不出现且存在魔法石出现)。这些东西我们可以 O ( m log ⁡ m ) O(m\log m) O(mlogm)排序后 O ( m ) O(m) O(m)预处理。
进一步地,考虑从大到小枚举 x x x,我们发现当且仅当 x x x达到某一个可能存在的价值时,对应的 P ( v i ≥ x ) P(v_i\ge x) P(vix)才会变化,于是我们把可能存在的价值从大到小排序,在处理 x x x改变到一个价值 v a l val val后的 Q Q Q之后,下一个会改变到的价值是 n x t nxt nxt,你可以直接在答案里累加 ( v a l − n x t ) × Q (val-nxt)\times Q (valnxt)×Q。当然,这个 n x t nxt nxt是可以等于 v a l val val的,我们应该在把所有的影响处理完后再加上对应的贡献,具体的话可以自己看代码理解一下。
我们发现, Q Q Q的改变总是这样的一种形式:
Q − > Q × 1 − P ( v i ≥ n x t ) 1 − P ( v i ≥ v a l ) Q->Q\times \frac{1-P(v_i\ge nxt)}{1-P(v_i\ge val)} Q>Q×1P(vival)1P(vinxt)
看上去只要求个逆元就好了,实际上有个问题: 1 − P ( v i ≥ v a l ) = 0 1-P(v_i\ge val)=0 1P(vival)=0怎么办?注意到 n x t ≤ v a l nxt\le val nxtval,而 P ( v i ≥ x ) P(v_i\ge x) P(vix) x x x减少而增大,所以当 1 − P ( v i ≥ v a l ) = 0 1-P(v_i\ge val)=0 1P(vival)=0时同样有 1 − P ( v i ≥ n x t ) = 0 1-P(v_i\ge nxt)=0 1P(vinxt)=0,于是我们不用管它。
这样我们就得到了一个 O ( m q ) O(mq) O(mq)的算法。然而并过不了,我们发现还有一个区间互不包含的条件没用,于是我们把这些区间按左端点排序,那么一个魔法阵会影响到的区间编号是一个连续的区间。我们又发现最后只需要求所有区间 a n s ans ans的和即可,于是我们可以用线段树同时维护所有区间的 Q Q Q,维护区间乘和整体求和即可,时间复杂度为 O ( m log ⁡ q ) O(m\log q) O(mlogq)
我傻逼的地方:注意某些点可能不会影响任何区间,我们直接不讨论这些点的影响,免得做线段树的时候RE…
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int n,m,q,tot,nowp[200010],l[200010]={0},r[200010]={0};
ll tag[400010],seg[400010];
ll old[200010],alldown[200010],partdown[200010],lastp[200010],lastdown[200010];
struct point
{
	int x;
	ll y,p;
}p[200010];
struct interval
{
	int l,r;
}t[200010];

bool cmp(point a,point b) {return a.y<b.y;}
bool cmpt(interval a,interval b) {return a.l<b.l;}

void update(int no,ll d)
{
	tag[no]=tag[no]*d%mod;
	seg[no]=seg[no]*d%mod;
}

void pushdown(int no)
{
	if (tag[no]!=1)
	{
		update(no<<1,tag[no]);
		update(no<<1|1,tag[no]);
		tag[no]=1;
	}
}

void pushup(int no)
{
	seg[no]=(seg[no<<1]+seg[no<<1|1])%mod;
}

void buildtree(int no,int l,int r)
{
	tag[no]=1;
	if (l==r) {seg[no]=1;return;}
	int mid=(l+r)>>1;
	buildtree(no<<1,l,mid);
	buildtree(no<<1|1,mid+1,r);
	pushup(no);
}

void modify(int no,int l,int r,int s,int t,ll d)
{
	if (l>=s&&r<=t)
	{
		update(no,d);
		return;
	}
	int mid=(l+r)>>1;
	pushdown(no);
	if (s<=mid) modify(no<<1,l,mid,s,t,d);
	if (t>mid) modify(no<<1|1,mid+1,r,s,t,d);
	pushup(no);
}

ll power(ll a,ll b)
{
	ll s=1,ss=a;
	while(b)
	{
		if (b&1) s=s*ss%mod;
		ss=ss*ss%mod;b>>=1;
	}
	return s;
}

int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;i++)
		scanf("%d%lld%lld",&p[i].x,&p[i].y,&p[i].p);
	for(int i=1;i<=q;i++)
		scanf("%d%d",&t[i].l,&t[i].r);
	
	sort(p+1,p+m+1,cmp);
	for(int i=1;i<=max(n,m);i++)
		alldown[i]=partdown[i]=1;
	for(int i=1;i<=m;i++)
	{
		if (lastp[p[i].x])
			partdown[i]=partdown[lastp[p[i].x]]*(mod+1ll-p[lastp[p[i].x]].p)%mod;
		else partdown[i]=1ll;
		lastp[p[i].x]=i;
		alldown[p[i].x]=alldown[p[i].x]*(mod+1ll-p[i].p)%mod;
	}
	p[0].y=0;
	
	sort(t+1,t+q+1,cmpt);
	int L=1,R=0;
	for(int i=1;i<=q;i++)
	{
		while(R<t[i].r) l[++R]=i;
		while(L<t[i].l) r[L++]=i-1;
	}
	while(L<=R) r[L++]=q;
	
	buildtree(1,1,q);
	for(int i=1;i<=n;i++)
		old[i]=1;
	
	ll ans=0;
	for(int i=m;i>=1;i--)
	{
		ll newp=((1ll-partdown[i]+alldown[p[i].x])%mod+mod)%mod;
		if (l[p[i].x])
		{
			modify(1,1,q,l[p[i].x],r[p[i].x],newp*power(old[p[i].x],mod-2)%mod);
			old[p[i].x]=newp;
		}
		ans=(ans+(p[i].y-p[i-1].y)*seg[1])%mod;
	}
	ans=(((ll)q*p[m].y-ans)%mod+mod)%mod;
	printf("%lld",ans);
	
	return 0;
}

2018.10.30

这次比赛我没考,所以代码就不写了,只讲讲思路。

T1 Slope

题目大意: 给定二维平面上的 n n n个点,再给定一个斜率 P / Q P/Q P/Q,求出一组点使得它们之间连线的斜率和 P / Q P/Q P/Q的差的绝对值最小。 n ≤ 2 × 1 0 5 n\le 2\times 10^5 n2×105
做法: 本题需要用到计算几何。
直接 O ( n 2 ) O(n^2) O(n2)暴力肯定会挂。但我们发现,假设我们找到了这一对点,我们过这两个点各作一条斜率为 P / Q P/Q P/Q的直线,则这两条直线之间应该不包含任何点,否则这就不是最优解。于是我们过每个点都作一条这样的直线,按作出的直线的截距排序,最优解只可能存在于这个顺序上相邻两个点之间,于是我们就以 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度解决了这一题。

T2 Tree

题目大意: n n n个点,每个点有键值 k i k_i ki和权值 v i v_i vi,要求用这些点构造出一棵关于键值的二叉查找树,使得树上任意相邻两点的键值的 gcd ⁡ \gcd gcd不为 1 1 1,并令 s u m i sum_i sumi为以 i i i为根子树内所有点 v i v_i vi的和,求合法的二叉查找树中 ∑ i = 1 n s u m i \sum_{i=1}^n sum_i i=1nsumi的最大值。 n ≤ 300 , k i ≠ k j ( i ≠ j ) n\le 300,k_i\ne k_j(i\ne j) n300,ki=kj(i=j)
做法: 本题需要用到区间DP。
首先,一棵树是二叉查找树的充要条件是,树的中序遍历和点按键值从小到大排序的排列是一样的。因此我们在这个排列上DP,令 f ( l , r , i ) f(l,r,i) f(l,r,i)为区间 [ l , r ] [l,r] [l,r]内的点构成一棵子树,并且根编号为 i i i时,子树内所有点 s u m sum sum之和的最大值,那么有状态转移方程:
f ( l , r , i ) = max ⁡ { f ( l , i − 1 , p ) + f ( i + 1 , r , q ) ∣ gcd ⁡ ( k p , k i ) ≠ 1 , gcd ⁡ ( k q , k i ) ≠ 1 } f(l,r,i)=\max\{f(l,i-1,p)+f(i+1,r,q)|\gcd(k_p,k_i)\ne 1,\gcd(k_q,k_i)\ne 1\} f(l,r,i)=max{f(l,i1,p)+f(i+1,r,q)gcd(kp,ki)=1,gcd(kq,ki)=1}
看上去复杂度为 O ( n 5 ) O(n^5) O(n5),但我们发现 p , q p,q p,q之间互不影响,所以我们分别枚举即可,时间复杂度为 O ( n 4 ) O(n^4) O(n4)。但这样还是过不了,我们又发现对于一个区间 [ l , r ] [l,r] [l,r],它对一个更大的区间有贡献,当且仅当这个更大的区间形成的子树以 l − 1 l-1 l1 r + 1 r+1 r+1为根。因此我们在处理一个区间时,枚举根 i i i,向下递归计算,并同时计算出对于 l − 1 , r + 1 l-1,r+1 l1,r+1两个点的贡献最大值即可,时间复杂度为 O ( n 3 ) O(n^3) O(n3)

T3 Forging

题目大意: 给定一棵 n n n个点的有根树,每个点有点权,边有边权, q q q次询问,每次询问一个点 x x x和一个数 k k k,问点 x x x的子树中和点 x x x的距离 ≥ k \ge k k的所有点的权值和。 1 ≤ n , q ≤ 1 0 5 1\le n,q\le 10^5 1n,q105
做法: 本题需要用到主席树。
这个题可以转化成,在DFS序上的一个区间内,求深度 ≥ d e p ( x ) + k \ge dep(x)+k dep(x)+k的所有点的权值和。因此我们对深度离散化,询问的时候二分算出实际上的询问区间,然后我们以DFS序为时刻,以深度为下标建立主席树即可做到 O ( n log ⁡ n ) O(n\log n) O(nlogn)

2018.11.3

这一次比赛我还是没有参加,也是只写一下思路。

T1 Flower

题目大意: 等比数列求前缀和,模数任意。
做法: 本题需要用到等比二分。
因为模数任意,直接套公式会有算不出来逆元的尴尬情况,所以等比二分即可。

T2 Mole

大模拟题,就不写了。

T3 Seer

题目大意: 给定一个 n n n个点, m m m条边的带边权的有向图,令一个环路的权值为,上面所有边权的几何平均数: ∏ i = 1 n a i n \sqrt[n]{\prod_{i=1}^n a_i} ni=1nai ,现在从点 i i i出发走一个回路,令 v i v_i vi为这条回路上可能包含的环路的最大权值,求所有 v i v_i vi n , m ≤ 5000 n,m\le 5000 n,m5000
做法: 本题需要用到SCC缩点+01分数规划+SPFA判负环。
首先把所有数都换成对数,众所周知,对数有以下优美的性质:
log ⁡ ( a ⋅ b ) = log ⁡ a + log ⁡ b \log (a\cdot b)=\log a+\log b log(ab)=loga+logb
log ⁡ ( a n ) = 1 n ⋅ log ⁡ a \log(\sqrt[n]a)=\frac{1}{n}\cdot \log a log(na )=n1loga
那么环的权值就可以表示成 1 n ⋅ ∑ i = 1 n a i \frac{1}{n}\cdot \sum_{i=1}^na_i n1i=1nai的形式了。要求出这个的最大值,显然01分数规划+SPFA判负环即可。
但是题目好像不是单纯让我们求最大环,我们还要考虑每个点能走到的最大环。熟悉图论的同学应该很清楚了,直接SCC缩点,对每个SCC进行01分数规划即可。

2018.11.4

这场比赛我又是没参加,还是只写思路,耶!

T1 Boom

题目大意: 想象炸弹人这个游戏,现在有一个 n × m n\times m n×m的网格( n × m ≤ 300 n\times m\le 300 n×m300),每个网格有一盏灯,初始的状态可能是开或关,一个炸弹会影响一个点以及它上下左右四个点的灯的状态,会将它们取反,问将所有灯关上至少需要多少个炸弹。
做法: 本题需要用到枚举。
显然,第一行要炸哪些点确定了,所有格子中哪些格子要炸就确定了,所以枚举第一行(当然要选较小的一维作为行),然后再 O ( n m ) O(nm) O(nm)算出下面哪些点要炸就行了,时间复杂度为 O ( 2 min ⁡ ( n , m ) n m ) O(2^{\min(n,m)}nm) O(2min(n,m)nm),因为 n m ≤ 300 nm\le 300 nm300,所以 min ⁡ ( n , m ) ≤ 18 \min(n,m)\le 18 min(n,m)18,可以通过此题。

T2 Farm

题目大意: 求比一个给定的排列 A A A字典序大的,恰好有 k k k个逆序对的长为 n n n的排列数目。 n , k ≤ 5000 n,k\le 5000 n,k5000
做法: 本题需要用到DP。
这题神似NOI2018Day1T2,思路也类似。先不管字典序的限制,考虑求长为 n n n的排列中,逆序对数恰好为 k k k的排列数目。
考虑我们平时求逆序对数的方法。一是从左往右插入数字,每次累加在它左边的比它大的数字的数目。但我们发现用这种做法难以转移,因此我们考虑另一种方法:将所有数字排序后从小到大插入,每次累加已经插入的在它右边的数字数目。令 f ( n , k ) f(n,k) f(n,k)为长为 n n n的排列中,逆序对数恰好为 k k k的排列数目,那么往里面新插入 n + 1 n+1 n+1后,新增的逆序对数可能是 0 0 0~ n n n中任何一个,因此有了状态转移方程:
f ( n , k ) = ∑ i = 0 n − 1 f ( n − 1 , k − i ) f(n,k)=\sum_{i=0}^{n-1}f(n-1,k-i) f(n,k)=i=0n1f(n1,ki)
可以用前缀和优化到 O ( n k ) O(nk) O(nk)
于是我们开始考虑字典序的限制。考虑枚举从左到右第一个大于给定排列 A A A的字符位置 i i i,那么显然在字典序的限制下,在这个位置上放一个比 A i A_i Ai大的数,然后这个位置后面的位置就可以随便选了。假设在这个位置上放的数是 x x x,那么整个排列的逆序对数,可以拆分成: 1 1 1 ~ i − 1 i-1 i1内部的逆序对数;一端在 1 1 1 ~ i − 1 i-1 i1,另一端在 i i i ~ n n n的逆序对数;一端是 i i i,另一端在 i + 1 i+1 i+1 ~ n n n的逆序对数; i + 1 i+1 i+1 ~ n n n内部的逆序对数。前面三个部分都可以用一些简单的预处理以 O ( n k ) O(nk) O(nk)的总时间复杂度得到,那么后面部分的逆序对数应该为 k − k- k上面那些部分的和 s s s,而后面部分是随便选的,相当于一个排列,因此累加 f ( n − i − 1 , k − s ) f(n-i-1,k-s) f(ni1,ks)入答案即可。这样我们就 O ( n k ) O(nk) O(nk)地解决了这一题。

T3 Pet

题目大意: n n n个点,每个点有一个坐标,现在有三种操作:将第 l l l r r r个点按照某一向量移动;将所有点的坐标还原为某个时刻上的状态;询问第 l l l到第 r r r个点中两两距离的最大值。这里的距离指,从点 ( x , y ) (x,y) (x,y)一次可以向八个方向走: ( 0 , 1 ) , ( 1 , 0 ) , ( 1 , 1 ) , ( 1 , − 1 ) , . . . (0,1),(1,0),(1,1),(1,-1),... (0,1),(1,0),(1,1),(1,1),...(把这里列出的四个全部取反就是另外四个),从一个点走到另一个点的最小步数。
做法: 本题需要用到主席树。
显然这里的距离就等于 max ⁡ ( ∣ x i − x j ∣ , ∣ y i − y j ∣ ) \max(|x_i-x_j|,|y_i-y_j|) max(xixj,yiyj)。于是对每一个区间维护区间 x , y x,y x,y的最大、最小值,用主席树+标记永久化计算即可。

2018.11.5

T1 Fib

题目大意: 给定一个数 A ( ≤ 1 0 9 ) A(\le 10^9) A(109),判断它能不能被拆分成两个斐波那契数的乘积。
做法: 斐波那契数列增长很快,直接暴力枚举即可。

T2 Equal

题目大意: 给定一棵树,多次询问到两个点 a a a b b b的距离相等的点有多少个。
做法: 经典倍增+LCA水题,不讲了。

T3 Tree

题目大意: 给定一棵树,问至少留下多少条边,使得存在至少 K K K个点,满足它们至少都和这 K K K个点中的另一个连通。 K ≤ N ≤ 1 0 5 K\le N\le 10^5 KN105
做法: 本题需要用到树形DP。
令保留的边数为 e e e,把思路转换一下,不考虑对一个 K K K最小的 e e e是多少,转而考虑对一个 e e e最大的 K K K是多少。
显然,一个点只要被保留的边覆盖,它就是合法点,那么为了使合法点数量最多,能同时连接两个不合法点就连接。那么问题来了,最多可以连接多少条这样的边呢?这显然是个最大匹配问题(因为树是二分图,所以我直接写最大匹配了),令最大匹配数为 x x x,那么当 e ≤ x e\le x ex时,最大的 K K K 2 e 2e 2e。而当 e > x e>x e>x时,每次只能连接一个不合法点了,这时最大的 K K K就是 2 e + ( x − e ) = x + e 2e+(x-e)=x+e 2e+(xe)=x+e。那么反过来得到,如果 K ≤ 2 x K\le 2x K2x,最小的 e e e ⌊ K + 1 2 ⌋ \lfloor\frac{K+1}{2}\rfloor 2K+1;如果 K > 2 x K>2x K>2x,最小的 e e e x + ( K − 2 x ) = K − x x+(K-2x)=K-x x+(K2x)=Kx
至于求最大匹配数 x x x,可以使用树形DP:令 f ( i , 0 / 1 ) f(i,0/1) f(i,0/1)为以 i i i为根的子树中,点 i i i没被/被匹配上的情况下的最大匹配数,那么有状态转移方程:
f ( i , 0 ) = ∑ s o n max ⁡ ( f ( s o n , 0 ) , f ( s o n , 1 ) ) f(i,0)=\sum_{son}\max(f(son,0),f(son,1)) f(i,0)=sonmax(f(son,0),f(son,1))
f ( i , 1 ) = max ⁡ { f ( i , 0 ) − max ⁡ ( f ( s o n , 0 ) , f ( s o n , 1 ) ) + f ( s o n , 0 ) + 1 } f(i,1)=\max\{f(i,0)-\max(f(son,0),f(son,1))+f(son,0)+1\} f(i,1)=max{f(i,0)max(f(son,0),f(son,1))+f(son,0)+1}
这样就可以 O ( n ) O(n) O(n)解决这一题了。当然,因为最大匹配等于最小点覆盖,而一个点覆盖的补集是独立集,这样就可以直接转化成我们熟悉的树上最大独立集来做,也是 O ( n ) O(n) O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值