2019牛客暑期多校训练营(第二场)(A(随机游走/概率)、B(杜教BM)、D(k大团)、E(线段树矩阵)、F(团/dfs暴搜)、H(次大01矩阵/悬线法or单调栈))

A.Eddy Walker(随机游走/概率)

T组样例,每次有一个下标0到Ni-1的环,

每次可以随机选择向前一步或向后一步,0后一步是Ni-1,Ni-1向前一步是0

从下标为0的点开始随机游走,直到所有下标都被访问过一次之后停止

停止的位置是Mi,则称这组样例对应事件发生

问前i组样例对应事件连续发生的概率,分数mod 1e9+7

 

思路来源:官方题解

连续发生显然为前i次的概率相乘,考虑每次的概率

如果点数大于1,从0出发后,就不可能再走到0;所以n==1时m=1,n>1时m=0

对于其他的点,由于随机游走,最终在每个点停留的概率相同,为\frac{1}{n-1}

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int modpow(int x,int n,int mod)
{
	int ans=1;
	for(;n;n/=2,x=1ll*x*x%mod)
	if(n&1)ans=1ll*ans*x%mod;
	return ans;
}
int t,n,m,ans;
int main()
{
	scanf("%d",&t);
    ans=1;
	while(t--)
	{
		scanf("%d%d",&n,&m);
		if(m==0)
        {
            if(n==1)ans=ans*1;
            else ans=ans*0;
        }
		else ans=1ll*ans*modpow(n-1,mod-2,mod)%mod;
        printf("%d\n",ans);
	}
	return 0;
}

B.Eddy Walker 2(杜教BM+中心极限定理期望性质)

T(1<=T<=10)组样例,第i次询问给出Ki(Ki<=1021)和Ni(-1<=Ni<=1e18)

一个人从0点出发,在数轴上向右走,

每次有1/K的概率向右走1步,有1/K的概率向右走2步,...,有1/K的概率向右走K步

问到达Ni点的概率是多少,Ni==-1时,代表Ni在无穷远处

 

①Ni==-1,也就是无穷远处

考虑走一步能走的距离的期望是\frac{K+1}{2},那么由于期望的性质,不妨认为每步都走\frac{K+1}{2}

那么看能不能到达N,就等价于看是从0,1,...\frac{K+1}{2}-1\frac{K+1}{2}个点哪个点出发的,

N只能从其中1个点出发到达,从而从这么多点选一个选中那个点的概率是\frac{2}{K+1}

群里请教大佬,严格证明需要中心极限定理,不懂,从略

 

②Ni不为无穷远,Ni<=1e18,线性递推式dp[N]=\frac{1}{k}(dp[N-1]+...+dp[max(N-k,0)])

放进杜教BM板子里搞一搞,预处理前2k项答案,搞出第N项取模mod答案

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll mod=1e9+7;
const int N=1024;

ll modpow(ll a,ll b,ll mod) {ll res=1;a%=mod; for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(ll x){return modpow(x,mod-2,mod);}

namespace linear_seq {
	#define rep(i,a,n) for (int i=a;i<n;i++)
	#define per(i,a,n) for (int i=n-1;i>=a;i--)	
	#define pb push_back
	#define mp make_pair
	#define all(x) (x).begin(),(x).end()
	#define fi first
	#define se second
	#define SZ(x) ((int)(x).size())
	typedef vector<int> VI;
	typedef pair<int,int> PII;
	typedef long long ll;
	const ll mod=1e9+7;
    const int N=10010;
    ll res[N],base[N],_c[N],_md[N];
 	ll modpow(ll a,ll b,ll mod) {ll res=1;a%=mod; for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
    vector<int> Md;
    void mul(ll *a,ll *b,int k) {
        rep(i,0,k+k) _c[i]=0;
        rep(i,0,k) if (a[i]) rep(j,0,k) _c[i+j]=(_c[i+j]+a[i]*b[j])%mod;
        for (int i=k+k-1;i>=k;i--) if (_c[i])
            rep(j,0,SZ(Md)) _c[i-k+Md[j]]=(_c[i-k+Md[j]]-_c[i]*_md[Md[j]])%mod;
        rep(i,0,k) a[i]=_c[i];
    }
    int solve(ll n,VI a,VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
//        printf("%d\n",SZ(b));
        ll ans=0,pnt=0;
        int k=SZ(a);
       // assert(SZ(a)==SZ(b));
        rep(i,0,k) _md[k-1-i]=-a[i];_md[k]=1;
        Md.clear();
        rep(i,0,k) if (_md[i]!=0) Md.push_back(i);
        rep(i,0,k) res[i]=base[i]=0;
        res[0]=1;
        while ((1ll<<pnt)<=n) pnt++;
        for (int p=pnt;p>=0;p--) {
            mul(res,res,k);
            if ((n>>p)&1) {
                for (int i=k-1;i>=0;i--) res[i+1]=res[i];res[0]=0;
                rep(j,0,SZ(Md)) res[Md[j]]=(res[Md[j]]-res[k]*_md[Md[j]])%mod;
            }
        }
        rep(i,0,k) ans=(ans+res[i]*b[i])%mod;
        if (ans<0) ans+=mod;
        return ans;
    }
    VI BM(VI s) {
        VI C(1,1),B(1,1);
        int L=0,m=1,b=1;
        rep(n,0,SZ(s)) {
            ll d=0;
            rep(i,0,L+1) d=(d+(ll)C[i]*s[n-i])%mod;
            if (d==0) ++m;
            else if (2*L<=n) {
                VI T=C;
                ll c=mod-d*modpow(b,mod-2,mod)%mod;
                while (SZ(C)<SZ(B)+m) C.pb(0);
                rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod;
                L=n+1-L; B=T; b=d; m=1;
            } else {
                ll c=mod-d*modpow(b,mod-2,mod)%mod;
                while (SZ(C)<SZ(B)+m) C.pb(0);
                rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod;
                ++m;
            }
        }
        return C;
    }
    int gao(VI a,ll n) {
        VI c=BM(a);
        c.erase(c.begin());
        rep(i,0,SZ(c)) c[i]=(mod-c[i])%mod;
        return solve(n,c,VI(a.begin(),a.begin()+SZ(c)));
    }
};
vector<int>ans;
ll dp[2*N],p,n;
int t,k;
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%lld",&k,&n);
		if(n==-1)
		{
			printf("%lld\n",2*inv(k+1)%mod);//2/(k-1)
			continue;
		}
		p=inv(k);
		dp[0]=1;//dp[n]=1/k dp[n-1] +1/k dp[n-2]+...+1/k dp[n-k]
		ans.clear();
		ans.push_back(dp[0]); 
		for(int i=1;i<=2*k;++i)
		{
			dp[i]=0;
			for(int j=max(i-k,0);j<i;++j)
			{
				dp[i]=(dp[i]+dp[j])%mod;
			}
			dp[i]=dp[i]*p%mod;//1/k
			ans.push_back(dp[i]); 
		}
		printf("%d\n",linear_seq::gao(ans,n));
	}
	return 0;
}

当然也可以选择用O(k^{2}logn)的矩阵快速幂优化,

去求前k项的系数,抄了一下杜教的代码,加了点注释

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=1;i<=n;++i)
#define mp make_pair
#define pb push_back
#define x0 gtmsub
#define y0 gtmshb
#define x1 gtmjtjl
#define y1 gtmsf
typedef long long ll;
//M为递推项系数个数 c为系数数组
//最终递推式为a[m]=c[0]a[0]+c[1]a[1]+...+c[m-1]a[m-1]  
const int M=1050,P=1000000007;
//求快速幂 只写一个系数即求逆元 
ll pw(ll x,ll y=P-2){
    ll s=1;
    for(;y;y>>=1,x=1ll*x*x%P)
        if(y&1)s=1ll*s*x%P;
    return s;
}
ll i,w,x,b,j,t,a[M],c[M],v[M],u[M<<1],ans;

//推a1...ak每项最终系数的矩阵快速幂 复杂度O(k^2 logn) 
//求a^n的k个a^1 a^k的系数 即求a^(n/2)的k个系数 然后乘在一起变成a^1 到a^(2k)的系数
//然后暴力把a^(k+1)到a^(2k)的系数再倒序下放到a^1 到 a^k上 
ll sol(ll n,ll m) {//求a[n] a[n]来自前m项递推式 
    //scanf("%d%d",&n,&m);
    n+=m-1;//整体右移m-1项 便于0-(m-1)向负的找系数 应为0 
    for(i=m-1;~i;i--)c[i]=pw(m);//c[m-1]到c[0] 每个1/m  
    for(i=0;i<m-1;i++)a[i]=0;a[m-1]=1;//a[0]-a[m-2]为0 a[m-1]实际为a[0]=0 
    for(i=0;i<m;i++)v[i]=1;
    for(w=!!n,i=n;i>1;i>>=1)w<<=1;//n=0时w=0 n!=0时w>1 w为不大于n的最大2的次幂 
    for(x=0;w;copy(u,u+m,v),w>>=1,x<<=1){//copy把[u,u+m]复制给v 
        fill_n(u,m<<1,0),b=!!(n&w),x|=b;//fill_n 把u.begin()的连续m<<1个位置 都覆盖成0 
        //如果n&w==0 b=0;n&w==w b=1 用两个!把非空判成了1 
        //如果w最高位为1 则x最低位|=1 
        if (x<m)u[x]=1;
        else {
        	//如果b==1 说明应u[i+j+1]+=v[i]*v[j] 这里v[1]==1 故省略 
        	//b==1时 直接向高1位加系数 就起到了先向u[i+j]加系数 再整体移到了u[i+j+1]这一位的作用  
			//类似快速幂的x^(2n+1)=x^(n)*x^(n)*x^1 
			//每次无论遇到0还是1 都有一个自乘左移的过程 所以要最高位第一次考虑 以此类推 
            for(i=0;i<m;i++)for(j=0,t=i+b;j<m;j++,t++)u[t]=((ll)v[i]*v[j]+u[t])%P;
            for(i=(m<<1)-1;i>=m;i--)for(j=0,t=i-m;j<m;j++,t++)u[t]=((ll)c[j]*u[i]+u[t])%P;//从2k-1到k 下放系数到k-1到0
			//注意u[2m-1]=c[0]*u[m-1]+...+c[m-1]*u[2m-2] 每个u[2m-1]的值v可以给u[m-1]下放v*c[0] 
        }
    }
    ans=0;
    for(i=0;i<m;i++)ans=(1ll*v[i]*a[i]+ans)%P;//推成前m阶系数 第i个系数是v[i] 值为a[i] 
    return ans;
}
int T,k;
ll n;
int main()
{
    for(scanf("%d",&T);T--;)
	{
        scanf("%d%lld",&k,&n);
        if(n==-1)printf("%lld\n",2ll*pw(k+1)%P);
        else printf("%lld\n",sol(n,k)%P);//
    }
    return 0;
}

D.Kth Minimum Clique(bitset暴力判完全子图/从小到大第k权值的团)

N(N<=100)的图,第i个点上有点权wi(0<=wi<=1e9),

输出权值从小到大第K(1<=K<=1e6)的团的权值,空集算一个权值为0的团

思路来源:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40895304

这种做法应该不是标程做法,但也算能莽过去的做法吧

用bitset维护当前集合已选的点,和下一步能向集合加入的点,也就是维护团中边的信息

每次只向大的搜,保证必含第i个点的状态内的团的点为[i,...),从而不重复

复杂度:似乎是O(klogk*n*\frac{n}{32}),但优先队列计一个清一个也到不了k,后者用bitset优化,极大地降了系数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=105;
int n,k,w[N];
struct node
{
	bitset<N>now;//当前已选点 
	bitset<N>next;//已选点和哪些点都连通 只能从next集中找下一个点
	ll sum;
	int maxid;//当前搜到了哪个点
	void clear()
	{
		now.reset();
		next.reset();
		sum=0;
		maxid=0;
	} 
}e[N];
bool operator<(node a,node b)
{
	return a.sum>b.sum;
} 
bitset<N>mp[N];
priority_queue<node>q;
char s[N];
ll solve()
{
	k--;//空集也算一个 
	if(k==0)return 0;
	for(int i=1;i<=n;++i)
	{
		e[i].clear();
		e[i].now.set(i);
		e[i].next=mp[i];
		e[i].sum=w[i];
		e[i].maxid=i;
		q.push(e[i]);
	}
	while(k&&!q.empty())
	{
		node t=q.top();q.pop();
		k--;
		if(k==0)return t.sum; 
		for(int i=t.maxid+1;i<=n;++i)
		{
			if(t.next.test(i))
			{
				node v=t;
				v.now.set(i);
				v.next&=mp[i];
				v.sum+=w[i];
				v.maxid=i;
				q.push(v);
			}
		}
	}
	if(k)return -1;
}
int main()
{
	while(~scanf("%d%d",&n,&k))
	{
		for(int i=1;i<=n;++i)
		mp[i].reset();
		for(int i=1;i<=n;++i)
		scanf("%d",&w[i]);
		for(int i=1;i<=n;++i)
		{
			scanf("%s",s+1);
			for(int j=1;j<=n;++j)
			{
				if(s[j]=='1')
				mp[i].set(j);
			}
		}
		printf("%lld\n",solve());
	}
	return 0;
}

官方题解:

二分权值maxV,判断<=maxV的团是否>=k个,找到最小的maxV

点权按从小到大排序,这样团转移到下一个团只需取lowbit对应的点,加上该点权值即可

复杂度O(k*log(maxV)),maxV<=n*maxwi=1e11,可惜WA得还没过,待补

E.MAZE(矩阵/线段树+dp)

给一个n*m(n<=5e4,m<=10)的01矩阵b,0是空地1是墙,

b[i][j]可以走到非墙的b[i+1][j]、b[i][j-1]和b[i][j+1],但不能回头

现有q个操作,操作分两种,每次给出x y z

1 x y 将b[x][y]取反

2 x y 询问b[1][x]到b[2][y]的路径方案数

思路来源:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40907986

线段树,叶子结点每个点维护一个m*m矩阵,

表示对于当前行l来说,哪两个点是连通的

考虑往上pushup的过程,就是通过跳板达到新的连通的过程

pushup一次时,a[i][j]*b[j][k]相当于,从第二行的i到第二行的j,再到第一行j,再到第一行k,方案唯一,

新的分之节点维护的是两行信息,...,依次类推,根节点是所有行的汇总信息,直接查询即可

更新时,更新叶子结点,然后O(m^3logn)完成pushup

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+10;
struct mat{
	const static int MOD = 1e9+7;
	const static int MAXN = 12;
    int c[MAXN][MAXN];
    int m, n;
    mat(){
    	memset(c,0,sizeof (c));
    }
    mat(int a, int b) : m(a), n(b) {
        memset(c, 0, sizeof(c));
    }
    void reset(int a,int b){
    	m=a; n=b;
    }
    void clear(){
		memset(c, 0, sizeof(c)); 
    }
    mat operator * (const mat& temp) {
        mat ans(m, temp.n);
        for (int i = 1; i <= m; i ++)
            for (int j = 1; j <= temp.n; j ++)
                for (int k = 1; k <= n; k ++)
                    ans.c[i][j] = (ans.c[i][j] + 1ll * c[i][k] * temp.c[k][j] % MOD) % MOD;
        return ans;
    }
}dat[N*4];
int n,m,q,a[N][15],x,y,z;
char s[15];
void pushup(int p)
{
	dat[p]=dat[p<<1]*dat[p<<1|1];
}
void op(int p,int l)
{
	int pos;
	dat[p].clear();
	for(int i=1;i<=m;++i)
	{
		pos=i;
		while(pos>=1&&a[l][pos]==0)
		{
			dat[p].c[i][pos]=1;//a[l][i]和a[l][pos]可达 
			pos--;
		}
		pos=i;
		while(pos<=m&&a[l][pos]==0)
		{
			dat[p].c[i][pos]=1;
			pos++;
		}
	}
}
void build(int p,int l,int r)//每一行进行操作 
{
	dat[p].reset(m+1,m+1);
	if(l==r)
	{
		op(p,l);
		return;
	}
	int mid=(l+r)/2;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void update(int p,int l,int r,int x)
{
	if(l==r)
	{
		op(p,l);
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid)update(p<<1,l,mid,x);
	else update(p<<1|1,mid+1,r,x);
	pushup(p);
}
int main()
{
	while(~scanf("%d%d%d",&n,&m,&q))
	{
		for(int i=1;i<=n;++i)
		{
			scanf("%s",s+1);
			for(int j=1;j<=m;++j)
			a[i][j]=(s[j]=='1');
		}
		build(1,1,n);
		for(int i=1;i<=q;++i)
		{
			scanf("%d%d%d",&x,&y,&z);
			if(x==1)
			{
				a[y][z]^=1;
				update(1,1,n,y);//对第y行重求 m^3logn 
			}
			else printf("%d\n",dat[1].c[y][z]);
		}
	}
	return 0;
}

F.Partition problem(构造两个团/dfs暴搜)

2N(1<=N<=14)个人,再给一个2N*2N的矩阵,第i行第j列的值为Vij(0<=Vij<=1e9)

让你把2N个人划分为大小均为N的两个团,使得同一团内成员i和成员j的权值Vij不计,

团1的任意成员i和团2的任意成员j的值Vij计一次(即,计Vij,就不计Vji)

求计入的答案的和的最大值

思路来源:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40890350

考虑只统计i<j的情况,也就是矩阵的右上角部分,

答案max=矩阵右上角元素之和-团内答案min,所以最小化两个团内成员答案之和

暴搜,开始2*n个人,没有团,搜每个的时候,枚举这一个是丢给第一个团还是丢给第二个团

丢入时,去统计加入的点和团内已存在的点之间权值的贡献,

由于团的大小<=n,所以,复杂度O(C_{2n}^{n}*n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=30;
const ll inf=1e18;
int n;
ll sum,ans,cur,a[N][N];
int s1[N],s2[N];//cnt代表个数,s代表step 
void dfs(int i,int cnt1)
{
	if(ans<=cur)return;
	if(i==2*n)
	{
		ans=cur;
		return;
	}
	if(cnt1<n)//放到第一个团里 
	{
		ll cal=0;
		for(int j=0;j<cnt1;++j)
		cal+=a[i][s1[j]];
		s1[cnt1]=i;
		cur+=cal;
		dfs(i+1,cnt1+1);
		cur-=cal;
	}
	if(i-cnt1<n)//放到第二个团里 
	{
		ll cal=0;
		for(int j=0;j<i-cnt1;++j)
		cal+=a[i][s2[j]];
		s2[i-cnt1]=i;
		cur+=cal;
		dfs(i+1,cnt1);
		cur-=cal;
	}
} 
int main()
{
	while(~scanf("%d",&n))
	{
		sum=0;
		cur=0;
		ans=inf;
		for(int i=0;i<2*n;++i)
		{
			for(int j=0;j<2*n;++j)
			{
				scanf("%lld",&a[i][j]);
				if(i<j)sum+=a[i][j];
			}
		}
		dfs(0,0);
		printf("%lld\n",sum-ans);
	}
	return 0;
}

H.Second Large Rectangle(次大01子矩阵/悬线法or单调栈)

N*M(1<=N,M<=1e3,N*M>=2)的01矩阵,求全为1的次大子矩阵,大小是1的个数和来决定的

思路来源:https://blog.nowcoder.net/n/2f8fe0e0f24248358d9194d9a111f0df

抄一发人家题解,在处理最大子矩阵的时候,可以用单调栈

考虑每个竖条都是直方图上的一个竖条,那就变成了单调栈的裸题,看是否能向两端扩展,求最大子矩阵面积

那么预处理竖条,如果当前位置是1,当前位置的值就是上方的值+1

然后这里处理的是次大子矩阵,考虑3 2 3 2的样例,是由2 2 2的矩阵更新出来的

再例如0 1 1 0 是由1的矩阵更新出来的,单调栈扫每个子矩阵只会扫一次,且不会扫子矩阵内部的矩阵

所以更新x*y的矩阵的时候,需要一并更新(x-1)*y和x*(y-1)的内部部分,来保证次大值一定会得到更新

 

而悬线法每个点都扫,所以得判矩阵是不是同一个矩阵,下午抄板子WA了若干发才过,

悬线法可以O(n^{2})地求子矩阵相关的问题,不会,待补

#include <bits/stdc++.h>
using namespace std;
int s[1005][1005];
char q[1005];
int L[1005],R[1005];
int maxn1, maxn2;
void calc1(int ans)
{
    if (ans > maxn1)
    {
        maxn2 = maxn1;
        maxn1 = ans;
    }
    else if (ans > maxn2)
        maxn2 = ans;
}
void calc(int x, int y)
{
    calc1(x * y);
    calc1(x * (y - 1));
    calc1((x - 1) * y);
}
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%s", q);
        for (int j = 1; j <= m; j++)
        {
            s[i][j] = q[j - 1] - '0';
            if (s[i][j] == 1)
                s[i][j] = s[i - 1][j] + 1;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        stack<int>st;
        st.push(0);
        s[i][0] = -2;
        for (int j = 1; j <= m; j++)
        {
            while (s[i][st.top()] > s[i][j])st.pop();
            L[j]=st.top()+1;
            st.push(j);
        }
        while(!st.empty())st.pop();
        st.push(m+1);
        s[i][m + 1] = -1;
        for(int j=m;j>=1;--j)
        {
        	while (s[i][st.top()] >= s[i][j])st.pop();
        	R[j]=st.top()-1;
        	st.push(j);
        }
        for(int j=1;j<=m;++j)
		calc(R[j]-L[j]+1,s[i][j]); 
    }
    printf("%d", maxn2);
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值