2020-2-13NOIP模拟测

写在前面的话:
我给自己跪下来顺带磕头求求自己考试先写暴力分。 T 2 T2 T2最低最低档暴力分好歹也有 20 p t s 20pts 20pts吧,在本次考试除了我基本都 A A A T 1 T1 T1 T 3 T3 T3的情况下写了暴力分就是 r k 1 rk1 rk1了好吗虽然这个rk1没啥用
还有就是要提升一下自己的做题速度 一开始看完题: T 3 T3 T3最可做于是开开心心地去码 T 3 T3 T3,没想到错了个sb地方调了俩小时。然后开 T 1 T1 T1,乍一看居然不知道怎么输出方案数(肯定是 C f Cf Cf打得还不够多思维能力还没锻炼出来),愣是从十一点想到了十一点半还没想出来。。码了个sb 50 p t s 50pts 50pts溜了,直接导致 T 2 T2 T2看到题就想出了最简单的暴力却没有时间写其实也因为好久没打那个板子导致现场推板子没时间了爆哭
以后考试能拿的分就先拿到手,赛下板子一定要多敲敲(别有思路不会打板子啊),还有就是多做做数据结构题,线段树调俩小时发现错哪的时候我就差亲手打自己了


糖果

题目描述

你有 nm 颗糖果。如果有 n 个小朋友来你家,你需要分给他们每人 m 颗糖;如果来的是 m 个小朋友,你就要分给每人 n 颗。 你打算把糖果分装在 k 个盒子里,每个盒子分别放几颗糖由你来决定。分糖果时,盒子是 不能被拆开的。(小朋友们拿到的只能是装着糖果的盒子,而不是散装的糖果) 你希望最小化盒子的数量 k,使得不论来了 n 个还是 m 个小朋友,你都能按照要求给他 们分糖果。

输入格式
第一行包含三个整数 n,m,t。
输出格式
如果 t = 0,你只需输出一个整数,表示最小的 k。 如果 t = 1,你需要在第一行输出一个整数,表示最小的 k;在第二行输出由空格隔开的 k 个整数,按照降序排列,表示每个盒子内的糖果数量。如果有多种方案使得 k 最小,你需要输 出其中字典序最大的。
样例输入 1 3 4 0
样例输出 1 6
样例输入 2 3 4 1
样例输出 2 6 3 3 3 1 1 1
样例解释 如果来了 3 个小朋友,分别给 {3,1},{3,1},{3,1};如果来了 4 个小朋友,分别给 {3},{3},{3},{1,1,1}。 另外,{3,3,2,2,1,1} 也是一组使得 k 最小的合法解,但它并不是字典序最大的。

70 p t s 70pts 70pts
寻找规律(手玩几组)可以发现数量就是 n ∗ m − g c d ( n , m ) n*m-gcd(n,m) nmgcd(n,m)
证明如下:

考虑一个 n 个黑点、m 个白点的二分图,第 i 个黑点表示第 i 个小朋友(总人数为 n 时), 第 j 个白点表示第 j 个小朋友(总人数为 m 时);一条边 (i,j) 表示这样一个盒子,这个盒子 在人数为 n 时属于第 i 个小朋友,在人数为 m 时属于第 j 个小朋友。 考虑这个图的某一连通分量,它包含 x 个黑点,y 个白点,则此连通分量中的边所代表的 盒子的糖果总数 = xm = yn,于是 x ≥ n/gcd(n,m),即每个连通分量至少包含 n/gcd(n,m) 个黑点,于是连通分量数量不超过 gcd(n,m),所以边数至少为 n + m−gcd(n,m),此即为盒 子数量的最小值。接下来我们会给出构造方案。(建议代入极值看极限情况证明)

100 p t s 100pts 100pts

由于要求字典序最大,考虑装的糖果最多的盒子,它装的糖数量最大为 n(否则在人数为 m 时这个盒子是分不出去的)。 其次我们要使大小为 n 的盒子尽可能多。考虑人数为 n 时,每人拿到 m 颗糖,其中至多有 ⌊m/n⌋ 个完整的 n,所以大小为 n 的盒子总数至多有 n·⌊m/n⌋。假如我们有了这么多盒子,此 时每个人还需要再拿 mmodn 颗糖;再反过来考虑人数为 m 的情况,这时有 n·⌊m/n⌋ 个人已 经完全满足,还剩下 m mod n 个人是空手的。于是我们将问题转化为了 n′ = m mod n,m′ = n 的子问题。 可以看出这样构造能使得字典序最大,同时用归纳法可以证明构造出来的盒子数量确实达 到下界。

考场上想到的是求 a n s ans ans可以考虑更相减损法的应用

#include 
#define ll long long
using namespace std;
int n,m,t;
ll ans;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
}
void mpi(int n,int m){
	ans+=m;
    if(n==m){return;}
	n-=m;
	if(n<m)swap(n,m);
	mpi(n,m); 
}
void work(int n,int m){
	if(m==0)return;
	int t=m*(n/m);
	for(int i=1;i<=t;++i){printf("%d ",m);}
	work(m,n%m);
}
int main(){
//	freopen("candy.in","r",stdin);
//	freopen("candy.out","w",stdout);
	n=read(),m=read(),t=read();
	if(n<m)swap(n,m);
	mpi(n,m);printf("%lld",ans); 
	if(t==0){return 0;}
	putchar('\n');
	if(t==1) {work(n,m);}
	return 0;
}

旅行

题目描述 Byteotia 由 n 座城市组成。城市之间连有 m 条道路,道路可以双向通行。其中第 i 条道 路连接 ui,vi 两座城市,通过这条道路需要花费 ti 个小时。城市和道路都从 1 开始编号。 Byteotia 的计时方法比较奇特,一天共有 K 个小时。我们假定一天的起始时刻是 0 点整。 Byteasar 打算在某天的 0 点整从城市 x 出发,前往城市 y。旅途中只能沿着道路行走,而 不允许原地休息。Byteasar 不在乎自己的旅行花费了多少天,他只希望到达 y 的时刻在一天中 尽可能早,即如果在某天的 T 点整 (0 ≤ T < K) 到达城市 y,他希望使得 T 尽可能小。 为了达到这一目标,Byteasar 的旅行路径中允许多次经过同一条道路,也允许多次经过同 一个城市(包括 x,y)。如果多次经过 y,最后一次到达 y 的时刻才算作到达时刻。 Byteasar 可能有多组旅行计划,他想寻求你的帮助。Byteotia 的计时方法也常常改变,所 以你需要对每一组 xj,yj,Kj 求出最小的 T。
输入格式
第一行包含三个整数 n,m,q。 接下来 m 行,每行三个整数 ui,vi,ti。 接下来 q 行,每行三个整数 xj,yj,Kj。
输出格式 输出包含 q 行,按顺序表示每个询问的答案。如果不存在 xj 到 yj 的路径,输出 NIE。
样例输入
6 5 3
1 2 3
3 4 7
4 6 9
3 5 1
5 6 1
1 3 5
1 2 4
6 3 8
样例输出
NIE
1
0

一句话题意:求出 x 到 y 的路径,路径长度模 K 后最小。

20 % 做 法 20\%做法 20%
K 为奇数时,任取一条 x 到 y 的路径,来回走 K 趟,答案一定是 0。 用并查集判断两点是否连通。
45 % 做 法 45\% 做法 45%
数据点 5 ∼ 9 中 K = 2,边权全是 1。 如果此连通分量不是二分图,则存在奇环,通过绕奇环一定可以得到答案为 0 的解。 如果是二分图,x,y 同色,答案为 0;不同色,答案为 1。 判断二分图并染色可以用并查集/遍历。
70 % 做 法 70\% 做法 70%
动态规划,f[i][j] 表示到达第 i 个点,当前已走过的路径长度模 K 为 j。复杂度 O(qKm)。
100 % 做 法 100\% 做法 100%
令 d 为 K 和连通分量中所有边权的最大公约数。则答案必然是 d 的倍数。最小答案一定 是 0 或 d(可以通过多绕几次调整得到)。判断方法和 45% 做法基本相同。
复杂度 O ( n + ( m + q ) l o g K ) O(n + (m + q)logK) O(n+(m+q)logK)

gf2:二分图判断颜色
bo[]==1

#include
#include
#include
#include
#include
#define MAX 222222 
using namespace std;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
struct edge{int u,v,w;}e[MAX];
int n,m,q;

int fa[MAX],g[MAX]={0},bo[MAX]={0};
int gf(int x){return x==fa[x]?x:fa[x]=gf(fa[x]);}//gf:并查集判断两点是否连通
int fa2[MAX*2];
int gf2(int x){return x==fa2[x]?x:fa2[x]=gf2(fa2[x]);}
void un(int x,int y,int C){//并查集union,同时更新边权信息,方便后面处理b 
	x=gf(x);y=gf(y);fa[x]=y;
	g[y]=gcd(gcd(g[y],g[x]),C);
}
int un2(int x,int y,int C){
	if(C){
		if(gf2(x)==gf2(y))return 0;
		fa2[gf2(x)]=gf2(y+n);
		fa2[gf2(y)]=gf2(x+n);
	}else{
		if(gf2(x)==gf2(y+n))return 0;
		fa2[gf2(x)]=gf2(y);
		fa2[gf2(x+n)]=gf2(y+n);
	}
	return 1;
}

int main()
{
	freopen("pod.in","r",stdin);
	freopen("pod.out","w",stdout);
	scanf("%d%d%d",&n,&m,&q);
	for (int i=1;i<=n;i++)fa[i]=i,fa2[i]=i,fa2[i+n]=i+n;
	for (int i=1;i<=m;i++){
		int x,y,w;scanf("%d%d%d",&x,&y,&w);
		if(rand()&1)swap(x,y);
		un(x,y,w);		
		e[i]=(edge){x,y,w};
	}
	for (int i=1;i<=m;i++){
		int t=g[gf(e[i].u)];t=t&-t;
		int ret=un2(e[i].u,e[i].v,e[i].w&t);
		if(!ret)bo[gf(e[i].u)]=1;//ret==0 不是二分图,存在奇环
	}
	while(q--){
		int x,y,mo;scanf("%d%d%d",&x,&y,&mo);
		if(gf(x)!=gf(y)){//并查集判断不连通 
			printf("NIE\n");
		}else{
			int d=gcd(g[gf(x)],mo);
			if((mo/d)&1)printf("0\n");//K是最大公约数的奇数次倍,
			else{
				int t=g[gf(x)]/d;
				if(bo[gf(x)])printf("0\n");//不是二分图,存在奇环 
				else{
					if(gf2(x)==gf2(y))printf("0\n");//是二分图但是不同色 
					else printf("%d\n",d);//只能绕到最小值为d 
				}
			}
		}
	}
	return 0;
}

说实话我T2是懵的,un2是什么鬼

区间GCD

比赛里唯一过了的题就是T3,还是这个比较友好

题目描述 最近 JC 同学刚学会 gcd,于是迷上了与 gcd 有关的问题。今天他又出了一道这样的题目, 想要考考你,你能顺利完成吗? 给定一个长度为 n 的字符串 s[1…n],串仅包含小写字母。对于区间 [l,r],你需要回答 s[l…r] 中有多少个长度为 3 的子序列组成了"gcd",即有多少组 (i,j,k) 满足 l ≤ i < j < k ≤ r,s[i] = ‘g’,s[j] = ‘c’,s[k] = ‘d’。
输入格式
第一行为一个字符串 s。 第二行为一个整数 q,表示询问数量。 接下来 q 行,每行两个整数 li,ri,表示一组询问。
输出格式
输出共 q 行,表示每一组询问的答案。答案请对 231 取模后输出。
样例输入
glygshcyjcdzy 3 1 11 2 11 2 10
样例输出
4 2 0
数据规模与约定 对于 20% 的数据,n ≤ 300,q = 1。 对于 40% 的数据,n ≤ 300,q ≤ 300。 对于 70% 的数据,n ≤ 4000,q ≤ 4000。 对于 100% 的数据,n ≤ 80000,q ≤ 80000。 串仅包含小写字母。1 ≤ li ≤ ri ≤ n。

80 ∼ 100 % 做 法 80∼100\% 做法 80100%—— 吉 丽 分 块 吉丽分块
我们可以把序列分成 n3 4 块,每一块含有 n1 4 个元素,开始我们可以通过枚举每一块中的三 元组,预处理出第 i 块中"gcd",“gc”,“cd”,‘g’,‘c’,‘d’ 的数量,知道这些数值之后我们就 可以合并答案了。对于每一个询问,我们找到这个区间内最大的整段的区间,这样它的左端和 右端最多各有 n1 4 个元素,我们可以暴力枚举每一个在同左端和同右端的三元组,求出"gcd", “gc”,“cd”,‘g’,‘c’,‘d’ 的数量,同时这个区间至多包含 n3 4 段,于是可以在 O(n3 4 ) 的时间 内合并信息。 复杂度 O((q + n)n3 4)。
80 ∼ 100 % 做 法 — — 莫 队 算 法 80∼100\% 做法——莫队算法 80100%
维护当前询问区间内"gcd",“gc”,“cd”,和’g’,‘c’,‘d’ 的数量。 在区间左/右端点插入/删除字母时,可以方便地更新上述信息。使用莫队方法对询问区间 离线排序,通过左右端点的移动即可逐一回答。 复杂度 O((n + q)√n)。
100 % 做 法 — — 线 段 树 100\% 做法——线段树 100%线
开一个线段树,每个线段树结点 [l,r] 维护区间内"gcd",“gc”,“cd”,‘g’,‘c’,‘d’ 的数 量。这样两个区间的信息是可以方便合并的。 复杂度 O(n + qlogn)。
100% 做法 记 L[i] 为 i 左边’g’ 的个数,R[i] 为 i 右边’d’ 的个数。 答案即为∑r i=ls[i] = ‘c’(R[i]−R[r]),将其展开后可以用若干个前缀和数组 维护。
复杂度 O(n + q)

第二第三种做法很类似
唯一需要注意的就是不仅 g c d gcd gcd需要合并, g c gc gc c d cd cd同样需要合并(没错调了快两个小时)

#include 
#define ll long long
using namespace std;
const int N=80050;
const ll Mod=(1<<31);
int q;
struct node{
	int ls,rs,l,r;
	ll g,c,d,gc,cd,gcd;
}tr[N<<2];
char s[N];
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
}
inline ll mul(int a,int b){return ((a%Mod) * (b%Mod)) %Mod;}
inline void build(int rt,int l,int r){
	if(l==r){
		tr[rt].l=l;tr[rt].r=r;
		tr[rt].g=tr[rt].c=tr[rt].d=tr[rt].gc=tr[rt].cd=0;
		if(s[l]=='g')++tr[rt].g;
		if(s[l]=='c')++tr[rt].c;
		if(s[l]=='d')++tr[rt].d;
		return;
	}
	tr[rt].l=l;tr[rt].r=r;
	int mid=(l+r)>>1;
	tr[rt].ls=rt<<1;tr[rt].rs=rt<<1|1;
	build(tr[rt].ls,l,mid);build(tr[rt].rs,mid+1,r);
	tr[rt].g = tr[tr[rt].ls].g + tr[tr[rt].rs].g;
	tr[rt].c = tr[tr[rt].ls].c + tr[tr[rt].rs].c;
	tr[rt].d = tr[tr[rt].ls].d + tr[tr[rt].rs].d;
	tr[rt].gc = (tr[tr[rt].ls].gc + tr[tr[rt].rs].gc + mul(tr[tr[rt].ls].g,tr[tr[rt].rs].c))%Mod;
	tr[rt].cd = (tr[tr[rt].ls].cd + tr[tr[rt].rs].cd + mul(tr[tr[rt].ls].c,tr[tr[rt].rs].d))%Mod;
	tr[rt].gcd = tr[tr[rt].ls].gcd%Mod + tr[tr[rt].rs].gcd%Mod + mul(tr[tr[rt].ls].gc,tr[tr[rt].rs].d) + mul(tr[tr[rt].ls].g,tr[tr[rt].rs].cd);
}
node query(int rt,int L,int R){
	int mid=(tr[rt].l+tr[rt].r)/2;
	if(tr[rt].l== L&&tr[rt].r== R){return tr[rt];}
	if(R<=mid)return query(tr[rt].ls,L,R);
	if(L>mid)return query(tr[rt].rs,L,R);
	else{
		node lx=query(tr[rt].ls,L,mid);node rx=query(tr[rt].rs,mid+1,R);node ans;
		ans.g=lx.g+rx.g;
		ans.c=lx.c+rx.c;
		ans.d=lx.d+rx.d;
		ans.cd=(lx.cd+rx.cd+ mul(lx.c,rx.d))%Mod;
		ans.gc=(lx.gc+rx.gc+ mul(lx.g,rx.c))%Mod;
		ans.gcd=(lx.gcd+rx.gcd+mul(lx.gc,rx.d)+mul(lx.g,rx.cd))%Mod;
		return ans;
	}
}
int main(){
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%s",s+1);
	int len=strlen(s+1);
	build(1,1,len);q=read();
	for(int i=1;i<=q;i++){
		int l=read(),r=read();
		ll ans=query(1,l,r).gcd%Mod;
		printf("%lld\n",ans%Mod);
	}
	return 0;
}

标程代码维护了一堆奇奇怪怪的前缀和 还是线段树好,可以无脑打

#include
#include
#include
#include
#include
using namespace std;
int n;
int m=128;
char s[88888];
int tmp1[200005],tmp2[200005],cnt[200005],sa[200005],*rank;
void doubling(){
	int *x=tmp1,*y=tmp2,num,i,j,len;
	for (i=0;i<m;i++)cnt[i]=0;
	for (i=0;i<n;i++)cnt[s[i]]++;
	for (i=1;i<m;i++)cnt[i]+=cnt[i-1];
	for (i=n-1;i>=0;i--)sa[--cnt[s[i]]]=i;
	for (num=i=1,x[sa[0]]=0;i<n;i++)x[sa[i]]=s[sa[i]]==s[sa[i-1]]?num-1:num++;
	for (len=1;num<n;len<<=1){
		for (j=0;j<len;j++)y[j]=n-len+j;
		for (i=0;i<n;i++)if(sa[i]>=len)y[j++]=sa[i]-len;
		
		for (i=0;i<num;i++)cnt[i]=0;//num
		for (i=0;i<n;i++)cnt[x[i]]++;
		for (i=1;i<num;i++)cnt[i]+=cnt[i-1];
		for (i=n-1;i>=0;i--)sa[--cnt[x[y[i]]]]=y[i];
		
		swap(x,y);
		for (num=i=1,x[sa[0]]=0;i<n;i++)
			x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+len]==y[sa[i-1]+len]?num-1:num++;
	}
	rank=x;
}
int l[88888]={0},r[88888]={0};
int sulr[88888]={0};
int sul[88888]={0};
int sur[88888]={0};
int su[88888]={0};
int main()
{
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%s",s+1);n=strlen(s+1);
	for (int i=1;i<=n;i++)l[i]=l[i-1]+(s[i]=='g');
	for (int i=n;i>=1;i--)r[i]=r[i+1]+(s[i]=='d');	
	for (int i=1;i<=n;i++){
		sulr[i]=sulr[i-1]+(s[i]=='c')*l[i]*r[i];
		sul[i]=sul[i-1]+(s[i]=='c')*l[i];
		sur[i]=sur[i-1]+(s[i]=='c')*r[i];
		su[i]=su[i-1]+(s[i]=='c');
	}
	int q;
	scanf("%d",&q);
	while(q--){
		int x,y;
		scanf("%d%d",&x,&y);
		int ans=sulr[y]-sulr[x-1]-l[x-1]*(sur[y]-sur[x-1])-r[y+1]*(sul[y]-sul[x-1])+(su[y]-su[x-1])*l[x-1]*r[y+1];
		ans&=(~(1<<31));
		printf("%d\n",ans);
	}
	return 0;
}

不是我说,标程代码是真的很奇怪

你可能感兴趣的:(联赛模拟)