2020牛客暑期多校训练营(第三场)

Clam and Fish

有鱼的话贪心捉鱼.

否则,有饲料则选饲料.

t y p e = 0 type=0 type=0且有饲料,则捉鱼.

最后剩余的饲料数/2即为可以多捉的鱼.(即前面为饲料,后面为鱼)

int n,ans,tot;
char s[N]; 

int main() {
	int _;qr(_); while(_--) {
		qr(n); ans=tot=0; scanf("%s",s+1);
		for(int i=1;i<=n;i++) {
			int c=s[i]-'0';
			if(c&2) ans++;
			else if(c&1) tot++;
			else if(tot) ans++,tot--;
		}
		pr2(ans+tot/2);
	}
	return 0;
}

Classical String Problem

如果我们把这个序列看作一个环的话,那么这个环的结构不变.

此时我们只需要记录原序列的第一个位置现在在哪里即可.


int n,m,pos;
char s[N];

int main() {
	scanf("%s",s); n=strlen(s);
	for(int i=0;i<n;i++) s[i+n]=s[i];
	qr(m); while(m--) {
		char op[5]; int x;
		scanf("%s",op); qr(x);
		if(op[0]=='M') pos+=x,pos=(pos+n)%n;
		else putchar(s[pos+x-1]),puts("");
	}
	return 0;
}

Operation Love

观察到只有大拇指的左边两点的距离为6,所以我们可以以他为参照点.

然后假如所有的点都在他们的右边则为右手.

否则为左手.

int n=20;

struct P {
	double x,y;
	P(double x=0,double y=0):x(x),y(y){};
	P operator -(P b) const {return P(x-b.x,y-b.y);}
	double operator *(P b) const {return x*b.y-b.x*y;}
	double dis(P b) const {return sqrt((x-b.x)*(x-b.x)+(y-b.y)*(y-b.y));}
	bool pd(P b,int x) const {return fabs(x-dis(b))<eps;}
} a[N];

double mult(P a,P b,P c) {return (a-c)*(b-c);}

int main() {
	int _;qr(_); while(_--) {
		for(int i=1;i<=n;i++) {
			double x,y;
			scanf("%lf%lf",&x,&y);
			a[i]=P(x,y);
		}
		a[n+1]=a[1]; a[n+2]=a[2];
		int x=0,y;
		for(int i=1;i<=n;i++)
			if(a[i].pd(a[i+1],6)) {
				if(a[i+1].pd(a[i+2],1)) x=i,y=i+1;
				else x=i+1,y=i;//x为掌心内侧的点. 
				break;
			}
		bool flag=1;
		for(int i=1;i<=n;i++)
			if(i^x&&i^y&&mult(a[x],a[y],a[i])>0) flag=0;
		if(flag) puts("right");
		else puts("left"); 
	}	
	return 0;
}

Points Construction Problem

先贪心弄出最小的答案(接近正方形).

然后缓慢增加答案,把从后往前塞到第一行的末尾.

如果还达不到,那么我们只好让一些点孤立了~~

int n,m,len,ans;
struct P {int x,y;} a[N];

void out() {
	puts("Yes");
	for(int i=1;i<=n;i++) pr1(a[i].x),pr2(a[i].y);
}

int main() {
	int _;qr(_); while(_--) {
		qr(n); qr(m); len=sqrt(n+0.1); ans=0; 
		if(m&1) {puts("No"); continue;}
		//设水平投影和竖直投影长度分别为a,b,则ans=2(a+b).要令ans最小就必须造出一个接近正方形的东西. 
		int x=1,y=1;
		for(int i=1;i<=n;i++) {
			a[i]=(P){x,y++};
			if(y>len) x++,y=1;
		}
		ans=2*(x-(y==1)+len);
		if(m<ans||m>4*n) {puts("No"); continue;}
		x=1; y=len+1;
		if(m==ans) {out(); continue;} 
		for(int i=n;i>len;i--) {//不够就把后面的拆到第一行,实现缓慢增长. 
			ans+=(a[i].y>1)*2;
			a[i]=(P){x,y++};
			if(ans==m)break;
		}
		if(ans==m) {out(); continue;}
		//如果还不够,那么我们考虑把块扔出.这样就会把贡献由2增加到4 
		x=y=1; 
		for(int i=1;i<=n;i++) {
			a[i]=(P){x,y++};
			if(ans<m) x++,ans+=2;
		}
		out();
	}
	return 0;
}

Two Matchings

很自然地想到要sort一下,然后交替求差为最小值.

然后我们考虑把序列按每4个或6个划分.

我们定义长度为n的划分的旋转为 n − r o t a t e n-rotate nrotate.

那么 n = 4 , a = 1 , 2 , 3 , 4 , n − r o t a t e = 2 , 3 , 4 , 1 n=4,a=1,2,3,4,n-rotate=2,3,4,1 n=4,a=1,2,3,4,nrotate=2,3,4,1

n = 6 , a = 1 , 2 , 3 , 4 , 5 , 6 , n − r o t a t e = 2 , 3 , 4 , 5 , 6 , 1 n=6,a=1,2,3,4,5,6,n-rotate=2,3,4,5,6,1 n=6,a=1,2,3,4,5,6,nrotate=2,3,4,5,6,1

可以发现这样和不旋转的配合是贡献最小的,代价和为 a [ n ] − a [ 1 ] a[n]-a[1] a[n]a[1].

如果是 n = 2 k ( k > 3 ) n=2k(k>3) n=2k(k>3)的话显然我们把它划分成更小的 n n n,可以使得中间的部分差不求,实现更小.

所以不难得到一个dp方程 f [ i ] = m a x ( f [ i − 4 ] + a [ i ] − a [ i − 3 ] , f [ i − 6 ] , a [ i ] − a [ i − 5 ] ) f[i]=max(f[i-4]+a[i]-a[i-3],f[i-6],a[i]-a[i-5]) f[i]=max(f[i4]+a[i]a[i3],f[i6],a[i]a[i5]).

最后 a n s = f [ n ] ∗ 2 ans=f[n]*2 ans=f[n]2.

int n,m,f[N],a[N];

int main() {
	int _;qr(_); while(_--) {
		qr(n);
		for(int i=1;i<=n;i++) qr(a[i]);
		sort(a+1,a+n+1);
		f[2]=1e9;f[4]=(a[4]-a[1]);
		for(int i=6;i<=n;i+=2)
			f[i]=min(f[i-4]+a[i]-a[i-3],f[i-6]+a[i]-a[i-5]);
		pr2(f[n]*2);
	}
	return 0;
}

Fraction Construction Problem

已知 a , b ( a , b > 0 ) a,b(a,b>0) a,b(a,b>0),求 c , d , e , f ( c , d , e , f > 0 ) c,d,e,f(c,d,e,f>0) c,d,e,f(c,d,e,f>0),使得 c d − e f = a b \dfrac{c}{d}-\dfrac{e}{f}=\dfrac a b dcfe=ba.

显然如果 t = gcd ⁡ ( a , b ) > 1 t=\gcd(a,b)>1 t=gcd(a,b)>1,我们直接 c = ( a + b ) / t , d = b / t , e = f = 1 c=(a+b)/t,d=b/t,e=f=1 c=(a+b)/t,d=b/t,e=f=1即可.

否则, c f − e d d f = a b \dfrac{cf-ed}{df}=\dfrac ab dfcfed=ba.

我们令 d f = b ( d ⊥ f ) df=b(d\bot f) df=b(df),根据裴蜀定理可知一定有解.

这个我们可以用线性筛求 d , f d,f d,f.

然后exgcd一下就好了.

int prime[N],tot,P[N]; bool v[N];
void get() {
	P[0]=P[1]=1;
	for(int i=2;i<N;i++) {
		if(!v[i]) prime[++tot]=i,P[i]=i;
		for(int j=1,k;(k=i*prime[j])<N;j++) {
			v[k]=1;
			if(i%prime[j]==0) {P[k]=P[i]*prime[j]; break;}
			P[k]=prime[j];
		}
	}
}

void exgcd(ll a,ll b,ll &x,ll &y) {
	if(!a) {x=y=1; return ;}
	exgcd(b%a,a,y,x); x-=b/a*y;
}

int main() {
	int T; qr(T); get(); while(T--) {
		ll a,b,c,d,e,f,t;
		qr(a); qr(b); t=gcd(a,b);
		if(t>1) {
			c=(a+b)/t; d=b/t; e=f=1;
			pr1(c); pr1(d); pr1(e); pr2(f);
			continue;
		}
		d=P[b]; f=b/d;
		if(f==1) {puts("-1 -1 -1 -1"); continue;}
		exgcd(f,d,c,e); e=-e;
		t=max(-c/d,-e/f)+1;
		if(t>0) c+=t*d,e+=t*f;
		c *= a ; e *= a;
		pr1(c); pr1(d); pr1(e); pr2(f);
	}
	return 0;
}


Operating on a Graph

并查集 + l i s t +list +list合并.显然 l i s t list list的总大小始终 ≤ 2 m \le 2m 2m.

介绍一下 l i s t list list的合并吧: ′ l i s t 1. s p l i c e ( p o s , l i s t 2 ) ′ 'list1.splice(pos,list2)' list1.splice(pos,list2),表示把 l i s t 2 list2 list2放到 p o s pos pos迭代器之前的位置,并清空 l i s t 2 list2 list2.

int n,m,fa[N],vis[N],num;
list<int> e[N];
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}

int main() {
	int T; qr(T); while(T--) {
		qr(n); qr(m);
		for(int i=0;i<n;i++) fa[i]=i,e[i].clear();
		for(int i=0,x,y;i<m;i++)
			qr(x),qr(y),e[x].pb(y),e[y].pb(x);
		qr(m); while(m--) {
			int x; qr(x);
			if(get(x)^x) continue;
			int sz=SZ(e[x]);
			vis[x]=++num;
			while(sz--) {
				int y=e[x].front(); e[x].pop_front();
				if(vis[y=get(y)]==num) continue;//这个团已经被合并
				e[x].splice(e[x].end(),e[y]);
				fa[y]=x; vis[y]=num;
			}
		}
		for(int i=0;i<n;i++) pr1(get(i));
		puts("");
	}
	return 0;
}

不知为啥 v e c t o r vector vector合并要更快?

Sort the Strings Revision

我们可以用st表预处理连续一段变化的最小值,然后用归并排序sort.复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),可以得到60分的好成绩…

#include<bits/stdc++.h>
#define R register 
using namespace std;
typedef long long ll;
const int N=2e6+10;

void qr(int &x) {scanf("%d",&x);}
void qr(ll &x) {scanf("%lld",&x);}

int T,n,lg[N],p[N],d[N],f[N][24],r[N],ans[N],tmp[N],s[N];
ll seed,a,b,mod;

inline int Min(const int A,const int B) {return A<=B?A:B;}

inline bool cmp(int x,int y) {
    int k=lg[y-x];
    int pos=Min(f[x][k],f[y-(1<<k)][k]);
    return pos==n || s[pos]<d[pos];
}

inline void Sort(int l,int r) {
    if(l==r) return ;
    int mid=(l+r)/2;
    Sort(l,mid);
    Sort(mid+1,r);
    for(R int i=l,j=mid+1,k=l;k<=r;k++)
        tmp[k]=(j>r|| (i<=mid&&cmp(ans[i],ans[j])))?ans[i++]:ans[j++];
    for(int i=l;i<=r;i++) ans[i]=tmp[i];
    // printf("%d %d:\n",l,r);
    // for(int i=l;i<=r;i++) printf("%d ",ans[i]);
    // puts("");
}

int main() {
    s[1]=1;
    for(int i=2;i<N;i++) lg[i]=lg[i>>1]+1,s[i]=i%10;
    qr(T); while(T--) {
        qr(n); 
        qr(seed); qr(a); qr(b); qr(mod);
        for(int i=0;i<n;i++) p[i]=i;
        for(int i=1;i<n;i++)
            swap(p[seed%(i+1)],p[i]),
            seed=(seed*a+b)%mod;
        qr(seed); qr(a); qr(b); qr(mod);
        for(int i=0;i<n;i++)
            d[p[i]]=seed%10,
            seed=(seed*a+b)%mod;

        for(int i=0;i<n;i++)
            if(s[p[i]]==d[p[i]]) f[i][0]=n;
            else f[i][0]=p[i];
        for(int j=1;j<=lg[n];j++)
            for(int i=0;i+(1<<j)<=n;i++)
                f[i][j]=Min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        
        for(int i=0;i<=n;i++) ans[i]=i;
        Sort(0,n); 
        for(int i=0;i<=n;i++) r[ans[i]]=i;
        
        b=10000019; mod=1000000007;
        ll res=0;
        for(R int i=n;i>=0;i--) res=(res*b+r[i])%mod;
        printf("%lld\n",res%mod);
    }
}

其实我们每次只用找到序列 p p p的最小值 p k p_k pk.
假设 p k ≠ d k p_k\ne d_k pk=dk.
那么 s 0 . . . s k s_0...s_k s0...sk的第 k k k位和 s k + 1 . . . s n s_{k+1}...s_n sk+1...sn一定不同.
我们可以根据这一位划分出两块的字典序大小,简单差分实现即可.
对于 p k = d k p_k=d_k pk=dk的情况,我们直接忽略即可.
所以我们只要对 p k ≠ d k p_k\ne d_k pk=dk的位置建一棵笛卡尔树,然后分治处理即可.总复杂度为 O ( n ) O(n) O(n).

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+10;

void qr(int &x) {scanf("%d",&x); }
void qr(ll &x) {scanf("%lld",&x);}

int T,n,p[N],c[N],lc[N],rc[N],sta[N],top,d[N];
ll seed,a,b,mod;

void solve(int x,int l,int r) {
    if(x==-1) return ;
    solve(lc[x],l,x);
    solve(rc[x],x+1,r);
    if(p[x]%10<d[x]) c[x+1]+=x-l+1,c[r+1]-=x-l+1;
    else c[l]+=r-x,c[x+1]-=r-x;
}

int main() {
    qr(T); while(T--) {
        qr(n);
        qr(seed); qr(a); qr(b); qr(mod);
        for(int i=0;i<n;i++) p[i]=i;
        for(int i=1;i<n;i++)
            swap(p[seed%(i+1)],p[i]),
            seed=(seed*a+b)%mod;
        qr(seed); qr(a); qr(b); qr(mod);
        for(int i=0;i<n;i++)
            d[i]=seed%10,
            seed=(seed*a+b)%mod;
        
        for(int i=0;i<=n;i++) c[i]=0,lc[i]=rc[i]=-1;
        top=0;
        for(int i=0;i<n;i++) {
            if(p[i]%10!=d[i]) {
                while(top&&p[sta[top]]>p[i]) lc[i]=sta[top--];
                if(top) rc[sta[top]]=i;
                sta[++top]=i;
            }
        }
        if(top) solve(sta[1],0,n);
        int tot=0; 
        for(int i=0;i<=n;i++) {
            c[i] += c[i-1];
            if(i&&p[i-1]%10==d[i-1]) 
                sta[i]=c[i]+(++tot);
            else sta[i]=c[i],tot=0;
            // printf("%d ",sta[i]);
        }
        a=10000019; mod=1000000007; ll res=0;
        // for(int i=0;i<=n;i++) printf("%d ",p[i]);
        for(int i=n;i>=0;i--) res=(res*a+sta[i])%mod;
        printf("%lld\n",res);
    }
}

Sorting the Array

好题.

观察1: r e t [ i ] ret[i] ret[i] [ 1 , i + m − 1 ] [1,i+m-1] [1,i+m1]除去 r e t [ 1... ( i − 1 ) ] ret[1...(i-1)] ret[1...(i1)]的最小值. 可以看作我们在维护一个大小为 m − 1 m-1 m1集合,每次操作加入一个数,并删除最小值.
观察2: 如果 j < i , r e t [ j ] > r e t [ i ] j<i,ret[j]>ret[i] j<i,ret[j]>ret[i],则 a [ i + m − 1 ] = r e t [ i ] a[i+m-1]=ret[i] a[i+m1]=ret[i].证明:在从 j j j i i i的过程中,凡是加入一个 < r e t [ i ] <ret[i] <ret[i]的数都会被删除,所以不会留到 i i i,所以可以确定 a [ i + m − 1 ] = r e t [ i ] a[i+m-1]=ret[i] a[i+m1]=ret[i].
观察3:如果我们把满足观察2的 i i i全部 d e l e t e delete delete,得到一个上升序列,设剩余元素有 t t t个,那么我们可以把剩余元素离散化成 [ 1 , t ] [1,t] [1,t],并且由于在求 r e t [ i ] ret[i] ret[i]时,集合中的数一定不是已经被 d e l e t e delete delete的数.
g ( n , a ) = ∣ { b ∣ f ( n , b , m ) = a } ∣ g(n,a)=|\{b|f(n,b,m)=a \}| g(n,a)={bf(n,b,m)=a},则有 g ( n , a ) = g ( t , { 1 , 2 , . . . . , t } ) g(n,a)=g(t,\{1,2,....,t\}) g(n,a)=g(t,{1,2,....,t})
观察4: 进行完转化后,对于数 i i i而言,可以放的位置为 [ 1 , min ⁡ ( i + m − 1 , t ) ] [1,\min(i+m-1,t)] [1,min(i+m1,t)],由于前面的数的可放区间更小,所以在放完前 i − 1 i-1 i1个数后,数 i i i的方案数为 d [ i ] = min ⁡ ( m , t − i + 1 ) d[i]=\min(m,t-i+1) d[i]=min(m,ti+1).

具体实现:
我们从后往前枚举,求有多少位不确定.(更具体的,求满足 s u m = ∏ i = x t d [ i ] ≥ k sum=\prod_{i=x}^t d[i]\ge k sum=i=xtd[i]k 的最大的 x x x)
然后我们前面的位( [ 1 , x − 1 ] [1,x-1] [1,x1])都是取最小---- a n s [ i ] = i ans[i]=i ans[i]=i.

然后我们对于每一位 i i i 从小到大考虑该放什么( j j j),
我们令 s u m = ∏ i = x t d [ i ] sum=\prod_{i=x}^t d[i] sum=i=xtd[i].
那么当扫到 j j j 时,以后 j j j 的合法选择就减小了1.(我们要证 i ≤ j + m − 1 ↔ i − ( m − 1 ) ≤ j , i − ( m − 1 ) i\le j+m-1\leftrightarrow i-(m-1)\le j,i-(m-1) ij+m1i(m1)j,i(m1)为第 i i i位最小的值,所以一定成立).

由于 m ≤ 2 m\le 2 m2,所以后面至多有 log ⁡ k \log k logk位,暴力就是 O ( n log ⁡ 2 k ) O(n\log ^2 k) O(nlog2k).

int n,m,ans[N],a[N],b[N],t,id[N];
bool vis[N];
ll k,sum;

int main() {
    int _; qr(_); while(_--) {
        qr(n); qr(m); qr(k); t=0;
        int mx=0;
        for(int i=1;i<=n;i++) ans[i]=vis[i]=0;
        for(int i=1,j=1;i<=n;i++) {
            qr(a[i]);
            if(a[i]<mx) ans[i+m-1]=a[i];
            else {
                mx=a[++t]=a[i];
                while(ans[j]) j++;
                id[t]=j++;
            }
        }
        int last=t; sum=1;
        while(sum<k) {
            last--;
            sum=sum*min(t-last+1,m);
        }
        for(int i=1;i<last;i++) ans[id[i]]=a[i];
        for(int i=last;i<=t;i++) b[i]=min(m,t-i+1);
        for(int i=last;i<=t;i++)
            for(int j=last;j<=t;j++) {
                if(!vis[j]) {
                    sum /= b[j]--;
                    if(k<=sum) {
                        vis[j]=1;
                        ans[id[i]]=a[j];
                        break;
                    }
                    k -= sum;
                    sum *= b[j];
                }
            }
        for(int i=1;i<=n;i++) pr1(ans[i]);
        puts("");
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值