2019牛客暑期多校训练营(第二场)A,B,D,E,F,H,J

以后每周打牛客重现赛

F. Partition problem

递归组合数枚举。

先把所有人当成红队,再组合数枚举7个人移到白队,每次移动改变的竞争值可以在On的复杂度内算出。

#include 
using namespace std;
typedef long long ll;
//typedef __int128 LL;
//typedef unsigned long long ull;
//#define F first
//#define S second
typedef long double ld;
typedef pair pii;
typedef pair pll;
typedef pair pdd;
const ld PI=acos(-1);
const ld eps=1e-9;
//unordered_mapmp;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
//#define a(i,j) a[(i)*(m+2)+(j)]  //m是矩阵的列数
//pop_back()
const int seed=131;
const int M = 1e5+7;
/*
int head[M],cnt;
void init(){cnt=0,memset(head,0,sizeof(head));}
struct EDGE{int to,nxt,val;}ee[M*2];
void add(int x,int y,int z){ee[++cnt].nxt=head[x],ee[cnt].to=y,ee[cnt].val=z,head[x]=cnt;}
*/
int n,N;
int a[30][30];
bool vs[30];
ll ma;
ll ct=0;
void cal(int x,int num,ll ans)//判断第x个人选不选,已经选了num个人到白队,目前分组的竞争力是ans 
{
	if(num>n||num+(N-x+1)

H。Second Large Rectangle

每一行跑一遍单调栈,找出以i为底边的四边形,的最大子矩阵,并把记录的矩形  h*w,(w-1)*h,也记录下来。

否则第二大矩形会找不到。因为单调栈执行过程中会把第二大当成不优解给排除,由于高-1在下一行会算到,我们只记录宽-1形成的小矩形即可

#include 
using namespace std;
typedef long long ll;
//typedef __int128 LL;
//typedef unsigned long long ull;
//#define F first
//#define S second
typedef long double ld;
typedef pair pii;
typedef pair pll;
typedef pair pdd;
const ld PI=acos(-1);
const ld eps=1e-9;
//unordered_mapmp;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
//#define a(i,j) a[(i)*(m+2)+(j)]  //m是矩阵的列数
//pop_back()
const int seed=131;
const int M = 1007;
const int N =1e7;
/*
int head[M],cnt;
void init(){cnt=0,memset(head,0,sizeof(head));}
struct EDGE{int to,nxt,val;}ee[M*2];
void add(int x,int y,int z){ee[++cnt].nxt=head[x],ee[cnt].to=y,ee[cnt].val=z,head[x]=cnt;}
*/
char S[M][M];
int h[M][M];//第i行第j列,向上最多扩展多少。
int s[M],w[M];
int ar[N],sz;
int main()
{
 
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%s",S[i]+1);
    int ct=0;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(S[i][j]=='1')h[i][j]=h[i-1][j]+1,ct++;
        else h[i][j]=0;
    }
    for(int i=1;i<=n;i++)
    {
        int p=0,ans=0;
        memset(s,0,sizeof(s));
        memset(w,0,sizeof(w));
        for(int j=1;j<=m+1;j++)
        {
            if(h[i][j]>s[p])s[++p]=h[i][j],w[p]=1;
            else {
                int wi=0;
                while(s[p]>h[i][j])
                {
                    wi+=w[p];
                    ar[++sz]=wi*s[p];
                    if(wi>1)ar[++sz]=(wi-1)*s[p];
                  	ans=max(ans,wi*s[p]);
                    p--;
                }
                s[++p]=h[i][j],w[p]=wi+1;
            }
        }
    }
    sort(ar+1,ar+1+sz);
    if(sz<=1)puts("0");
    else printf("%d\n",ar[sz-1]);
    return 0;
}
/*
3 7
1010100
1111100
1111111
 
1 4
1110
*/

[A. Eddy Walker]

打表找规律或者推公式:

1:打表

#include 
using namespace std;
int vs[100],nm[100];
int main()
{
//	ios::sync_with_stdio(false);
//  	cin.tie(0);

    srand((unsigned int)(time(NULL)));
    int n;
    scanf("%d",&n);
    for(int i=1;i<=100000;i++)
    {
    	int now=rand()%2;
    	memset(vs,0,sizeof(vs));
    	int tp=0,ct=1;
		vs[0]=1;
    	while(ct

发现规律:到每个点的概率都相同,到0点概率为0

AC代码:

#include 
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll qpow(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1)ans=a*ans%mod;
		a=a*a%mod;
		b/=2;
	}
	return ans%mod;
}
int main()
{

	int t;
	ll ans=1;
	scanf("%d",&t);
	while(t--)
	{
		int n,m; 	
		scanf("%d %d",&n,&m);
		ll tp;
		if(m==0)tp=0;else tp=n-1;
		if(n==1)tp=1;
		ans=(ans*qpow(tp,mod-2))%mod;
		printf("%lld\n",ans);
	}
	return 0;
}

 

推公式:

假设答案是:f(n,m)。

1:首先f(n,0)=0.(n>1),走完其他点就直接停了

2:然后  f(n,i)=f(n,n-i+1).   因为左右走概率相同,最后停在前面一个的概率,肯定等于停在后面一个的概率(注意它是个环)。

3:最后,f(n,i)=f(n,(i+n-1)%n)+f(n,(i+1)%n)/2,即停在一个点的概率等于停在其前面一个点的概率+后面一个点的概率除以二。

因为:

最后停在点i。上一步必定是在i+1,或者i-1.

 

假设上一步是i+1,如果走完除了i点以外的所有点,且最后一步是i+1的概率是x1。那么在这x1概率中,有1/2的概率走向i点,1/2的概率到i+2(不会再回到i+1点,最后一步到i+1所有的概率已经是x了)所以最后一步停在i 点的概率:p+=1/2*x。

同理  p+=1/2*x2(最后一步停在i-1,且已经走完除了i的所有点的概率)

所以3结论成立。

由2、3可推出f(n,i)  任取  (1

所以得出:

f(1,0)=1;

f(n,m)=1/n;

f(n,0)=0;

 D:Kth Minimum Clique

最大集团数2^100.

直接求第k小不好求,枚举出集团数会T。

求第K小,一般可以看看能不能从第一小开始递推。

我们发现:最小的是空集。

然后往空集这个集团加一个点。

得到的集团中选个最小的(此时的集团一定是第二小)再往这个集团加一个可行点。

然后再取最小的集团(此时的集团一定是第三小)。

当前最小权值集团,一定是第i小,i为执行次数+1.

新的集团一定要通过当前集团一个新的点得到。而点的权值为正数。所以当前最小一定是第i小。

1.取最小集团这个操作明显可以用优先队列来做。

2.而判断一个点能否可以加入一个集团里,为了On的判断出来,我们可以用bitset进行维护。

bitset中1代表集团中包含的点。判断某点能否加入集团中,只需判断,这个集团的点是否都在这个点能连的点中。

所以读入连边时直接用bitset维护即可。

3.为了防止重复我们可以记录每个集团点编号最大的,加点时只能选择id更大的点加入,刚好是组合型枚举

#include 
using namespace std;
typedef long long ll;
ll w[110];
struct node{
	ll val;
	int lst;
	bitset<101>s;
	bool operator <(const node &r)const{
		return val>r.val;
	}
};
priority_queueq;
bitset<101>e[101]; 
char S[101];
int main()
{
	//freopen("3.in","r",stdin);
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
	for(int i=1;i<=n;i++){
		scanf("%s",S+1);
		for(int j=1;j<=n;j++)
		if(S[j]=='1')e[i][j]=1;else e[i][j]=0;
	}
	if(k==1){
		printf("0\n");return 0;
	}
	bitset<101>p;
	q.push(node{0,0,p});
	int nm=0,ct=0;
	//由于优先队列里都是正数,
	//任意数再加上一个正数必然大于当前存的权值最小的集团的权值
	//。所以堆头一定是当前最小权值集团 
	while(!q.empty())
	{
		node tp=q.top();q.pop();
		nm++;
		if(nm==k)
		{
			printf("%lld\n",tp.val);
			return 0;
		}
		bool f=false;
		for(int i=tp.lst+1;i<=n;i++)//从当前点后面的点进行选择,去重 
		{
			bitset<101>now(tp.s);
			if((e[i]&now)==now)//这个点可以加入tp这个小集团里 
			{
				now[i]=1;
				q.push(node{tp.val+w[i],i,now});
			}
		}
	}
	printf("-1\n");
	return 0;
}
/*
5 13
545656160 714825755 534642371 950076512 270441846
00111
00110
11011
11101
10110

1484718883

*/

B.Eddy Walker 2

 

https://blog.csdn.net/yiqzq/article/details/96635574

学习这个大佬的思路

https://www.cnblogs.com/Yinku/p/11220848.html

抄了这个大佬的BM板子

这题就结束了。。。。瑟瑟发抖

思路蛮简单的,主要是k=-1时不好做。

https://www.zhihu.com/question/336062847?utm_source=qq&utm_medium=social&utm_oi=1017107436351676416

自行移步知乎进行学习。。考场上还是靠直觉或者打表吧。

下面说下我的比较通俗的理解

在足够长时间之后,平均每次前进k+1/2步,因此平均停留次数就是2/(k+1);

或者更简单的说,无穷大后,每(k+1)/2个点会到达一次。即最后到达的点是 :(k+1)/2,(k+1)/2 *2,(k+1)/2*3……

而无穷大的终点落在在某个我们达到点的概率是1/[(k+1)/2]=2/(k+1);(相当于终点不固定随机定)

#include
using namespace std;
#define rep(i,a,n) for (int i=a;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 VI;
typedef long long ll;
typedef pair PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
// head

ll n,k;
namespace linear_seq {
    const int N=10010;
    ll res[N],base[N],_c[N],_md[N];

    vector 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]+...
        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<=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*powmod(b,mod-2)%mod;
                while (SZ(C)v;
    int nCase;
    scanf("%d", &nCase);
    while(nCase--){
        scanf("%lld%lld",&k, &n);
        memset(f,0,sizeof(f));
        v.clear();
        f[1]=1;v.push_back(f[1]);
        for(int i=2;i<=k*2;i++)
        {
        	for(int j=max(1ll,i-k);j

E:MAZE

dp思路:

dp[i][j]:经过(i-1,j)到达(i,j)的方案数。

如果不带修改,从(1,a)->(n,b).  初值:dp[1][a]=1;   输出dp[n+1][b].(只有这样才能保证最后一行也会有贡献)

转移:dp[i][j]=sum(dp[i-1][L],dp[i-1][R]).  L,R为:(i-1,j)左边0的位置,右边0的位置。如果i-1,j为1,则dp[i][j]也为0(即不可能从i-1,j转移到i,j)

然后上述转移可以看成矩阵乘法:

比如i-1行为:011011

则dp[i][0],dp[i][1]……dp[i][m]=

0 0 0 0 0 0
0 1 1 0 0 0
0 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 1 1
0 0 0 0 1 1

*{dp[i-1][0],dp[i-1][1]……dp[i-1][m]}.

为什么要转化成矩阵乘法?

因为可以用线段树维护,这样更新查询就非常快的做出来了!!

#include 
using namespace std;
typedef long long ll;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
const int M = 5e4+7;
const int mod=1e9+7;
int n,m,q;
char s[M][15];
struct node{
	ll ma[10][10];
}tr[M<<2],P;
node mul(node a,node b)
{
	node tp={0};
	for(int i=0;i<=9;i++)
	for(int j=0;j<=9;j++)
	for(int k=0;k<=9;k++)
	tp.ma[i][j]=(tp.ma[i][j]+a.ma[i][k]*b.ma[k][j]%mod)%mod;
	return tp;
}
void tb(int o,int x)
{
	tr[o]={0};
	int pr=0;
	for(int k=0;km)tp=mul(tp,qu(rs,m+1,r,x,y));
	return tp;
}
int main()
{
	for(int i=0;i<=9;i++)
	for(int j=0;j<=9;j++)
	if(i==j)P.ma[i][j]=1;
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	bd(1,1,n);
	while(q--)
	{
		int op,l,r;
		scanf("%d%d%d",&op,&l,&r);
		if(op==1)
		{
			if(s[l][r]=='0')s[l][r]='1';else s[l][r]='0';
			up(1,1,n,l);
		}
		else
		{
			node tp=qu(1,1,n,1,n);
			node a={0};
			a.ma[0][l-1]=1;
			a=mul(a,tp);
			printf("%lld\n",a.ma[0][r-1]);
		}
	}
	return 0;
}

JSubarray

核心思想:合并有用区间。

虽然有1e9个数,但很容易想到:有一些-1是永远也用不上的

我们利用两个dp来预处理,找到有用的区间。

对区间的前缀和进行找正序对操作(即树状数组经典题)

然鹅树状数组会T。

然后就到了这题比较难的地方了

对于一个有效区间[L,R]

所有区间和最多3e7。

我们只能用On的算法去跑(现场赛的机器我感觉加个log也无所谓,牛客可惜了)

这题跟经典逆序对题目有点不一样:每次前缀和数值变化最多1,这就意味着我们可以开一个数组存数字出现次数,每次往后遍历,正序对数只会改变一个数字的出现次数。

具体看代码即可。

#include 
#include 
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int M=3e7+15;
int n;
int l[N],r[N],f[N],b[N];
//f[i]  以第i段1区间的右端点为右端点的区间中,区间和最大的是多少
//b[i]  以第i段1区间的左端点为左端点的区间中,区间和最大的是多少
int ct[M],nm1,nm2,sum[M];
const int base =1e7+3;
//ct[i]   在当前有效区间内,前缀和数值中,i-base已经出现的次数
//nm2[i]  在当前有效区间内,处理到第i位时,i位之前的数有多少数不大于第i位数
//nm1[i]  在当前有效区间内,处理到第i位时,i-1位之前的数有多少数不大于第i-1位数
//sum[i]  在当前有效区间内,前缀和。
int main()
{
    //freopen("12.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d%d",&l[i],&r[i]),l[i]++,r[i]++;
    ll pr=0;
    l[n+1]=r[n+1]=2e9;
    for(int i=1;i<=n;i++)
    {
        f[i]=max(f[i-1]-(l[i]-r[i-1]-1),0)+r[i]-l[i]+1;
        int j=n-i+1;   
        b[j]=max(b[j+1]-(l[j+1]-r[j]-1),0)+r[j]-l[j]+1;
    //  cout<=l[pos+1]-r[pos]-1)pos++,ans+=r[pos]-l[pos]+1;//判断关系不要弄错,左边的右端点往左扩展的,和右边左端点往右扩展的,能否把中间的-1掩盖住
    //  cout<sum[p-1])nm2=nm1+ct[sum[p-1]+base];
            else nm2=nm1-ct[sum[p]+base];
            pr+=nm2;
            ct[sum[p]+base]++;
        //  cout<

你可能感兴趣的:(多校----牛客/hdu)