zr2019暑期高端峰会AB组day1

zr2019暑期高端峰会AB组day1

A. 小K与集合

这道题我在考场起码思考了两个小时,总觉得能想出什么,但其实就差一步了,我真是又傻逼又菜,连暴力都写T了,期望得分45,实际得分15,挂掉了暴力,判掉了全部元素相同和不同。

心路历程

以下是大概我前一个小时想的内容:

  • 首先题目说什么什么有概率,又问必定成功的方案,其实就是要我们每一步都考虑最坏情况,我们想象对面有个人每次尽可能的阻止我达成目标,留下最废的一堆给我。
  • 由于每次都最坏,所以分组不能太集中,要尽量平均,最小化损失,否则如果一堆数放一组直接被对方删掉就GG了
  • 某个时刻当1的个数 ≥ k \geq k k时,我就赢了,我在1放在每一组里都有就行。这个情况可以稍微推广到,比如有至少k个1必胜,由于k个2在一轮之后我只能保证一个1存活下来,所以至少有 k 2 k^2 k2个2必胜,推广到至少有 k i k^i ki个i必胜,于是我们就判掉了所有元素相同和不同(不同无解)
  • (下面就想偏了)当1的个数小于k时虽然不能必胜,但是我可以前面的几组只单放一个1,然后剩下很多再在后面几组平均发放,于是就有点想直接模拟这个过程,但是万一不能平均,有余数怎么办,于是就疯狂想这个,然后我人就没了

正解

其实第三点中的本质就是k个i可以换一个i-1,于是这就像往前进位,看看能不能换来一个0,就是0位上有没有数,然后这题就没了。
然后我才突然发现我判断全部元素相同的过程(118-123行)其实就是在进位啊!!我竟然这么傻逼

考场的又臭又长菜鸡代码
#include<bits/stdc++.h>
#define FOR(i,a,b) for (register int i=(a);i<=(b);i++)
#define For(i,a,b) for (register int i=(a);i>=(b)i;--)
#define mem(i,j) memset(i,j,sizeof(i))
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+5;
int t,n,k,a[N],f[N],pos1[N],pos2[N],vis[N],nxt[N],last[N],g[N];
vector <int> T[N];
int task1,task2,task3;
//Task1
int s[1<<11],ans1[11],ans2[11];
vector <int> G[11];
//
//Task3
map<int,int> mp;
//
int read()
{
	int x=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
void Judge()
{
	task1=0;
	if (n<=10&&k==2) task1=1;
	task2=1;
	FOR(i,1,n) if (a[i]!=a[1]) task2=0;
	task3=1;
	FOR(i,1,n)
	{
		if (mp[a[i]]) task3=0;
		mp[a[i]]++;
	}
	FOR(i,1,n) mp[a[i]]=0;
	return;
}
int DFS(int siz,int *A)
{
	FOR(i,1,siz) if (A[i]==0) return 1;
	if (siz<k) return 0;
	int cnt=0;
	FOR(i,1,siz) if (A[i]==1) cnt++;
	if (cnt>=2) 
	{
		if (siz==n)
		{
			int pos=0;
			FOR(i,1,siz) if (A[i]==1) pos=i;
			FOR(i,1,siz)
			{
				if (i!=pos) ans1[++ans1[0]]=i;
				else ans2[++ans2[0]]=i;
			}
		}
		return 1;
	}
	int b[2][11],tag[2];
	int len=G[siz/2].size();
	FOR(i,0,len-1)
	{
		int msk=G[siz/2][i];
		int p1=0,p2=0;
		tag[0]=tag[1]=0;
		FOR(j,0,siz-1)
		{
			if ((msk>>j)&1) b[0][++p1]=A[j+1]-1;
			else b[1][++p2]=A[j+1]-1;
		}
		tag[0]=DFS(siz/2,b[0]);
		tag[1]=DFS((siz+1)/2,b[1]);
		if (tag[0]&&tag[1])
		{
			if (siz==n) 
			{
				FOR(j,0,siz-1)
				{
					if ((msk>>j)&1) ans1[++ans1[0]]=j+1;
					else ans2[++ans2[0]]=j+1;
				}
			}
			return 1;
		}
	}
	return 0;
}
void Init()
{
	FOR(i,0,(1<<10)-1)
		FOR(j,0,10-1)
			if ((i>>j)&1) s[i]++;
	return;
}
void Task1()
{
	ans1[0]=ans2[0]=0;
	FOR(i,0,(1<<n)-1) G[s[i]].pb(i);
	if (DFS(n,a))
	{
		printf("1\n");
		printf("%d ",ans1[0]);
		FOR(i,1,ans1[0]) printf("%d ",ans1[i]);
		printf("\n");
		printf("%d ",ans2[0]);
		FOR(i,1,ans2[0]) printf("%d ",ans2[i]);
		printf("\n");
	}
	else printf("0\n");
	return;
}
void Task2()
{
	int cnt=n,now=a[1],ok=0;
	while (cnt)
	{
		cnt/=k;
		now--;
		if (now==1&&cnt>=k) ok=1;
	}
	if (ok)
	{
		int cnt1=n%k,cnt2=0;
		printf("1\n");
		if (cnt1) FOR(i,1,cnt1)
		{
			printf("%d ",n/k+1);
			FOR(j,1,n/k+1) printf("%d ",++cnt2);
			printf("\n");
		}
		FOR(i,1,k-cnt1)
		{
			printf("%d ",n/k);
			FOR(j,1,n/k) printf("%d ",++cnt2);
			printf("\n");
		}
	}
	else printf("0\n");
	return;
}
void Task3()
{
	printf("0\n");
	return;
}
void Solve()
{
	int ok=0,tag=0,mx=0;
	FOR(i,1,n) f[a[i]]++;
	FOR(i,1,n) if (f[i]>=k) tag=1,mx=max(mx,i);
	if (!tag)
	{
		printf("0\n");
		return;
	}
	tag=0;
	FOR(i,1,n) g[i]=f[i];
	FOR(i,1,n/k)
	{
		int tmpk;
		if (g[1+tag]>=k) 
		{
			ok=1;
			break;
		}
		else tmpk=k-g[1+tag];
		FOR(j,1,mx-tag) 
			g[j+tag]/=tmpk;
		tag++;
	}
	if (ok)
	{
		printf("1\n");
		if (f[1]>=k)
		{
			int now1=0;
			FOR(i,1,n) vis[i]=0;
			FOR(i,1,n) if (a[i]==1&&now1<k-1)
			{
				now1++;
				vis[i]=1;
				printf("1 %d\n",i);
			}
			printf("%d ",n-k+1);
			FOR(i,1,n) if (!vis[i]) printf("%d ",i);
			printf("\n");
		}
		else 
		{
			FOR(i,1,n) vis[i]=0;
			FOR(i,1,n) if (a[i]==1) printf("1 %d\n",i),vis[i]=1;
			int tmpk=k-f[1];
			FOR(i,2,tmpk) nxt[i]=i-1;
			nxt[1]=tmpk;
			FOR(i,1,n) last[i]=1;
			FOR(i,1,tmpk) T[i].clear();
			FOR(i,1,n) if (!vis[i]) T[nxt[last[a[i]]]].pb(i),last[a[i]]=nxt[last[a[i]]];
			FOR(i,1,tmpk)
			{
				int len=T[i].size();
				printf("%d ",len);
				FOR(j,0,len-1) printf("%d ",T[i][j]);
				printf("\n");
			}
		}
	}
	else printf("0\n");
	return;
}
int main()
{
//	freopen("data1.in","r",stdin);
	Init();
	t=read();
	while (t--)
	{
		n=read(),k=read();
		FOR(i,1,n) a[i]=read();
		Judge();
		if (task1) Task1();
		else if (task2) Task2();
		else if (task3) Task3();
		else Solve();
	//	printf("//\n");
	}
	return 0;
}
赛后的代码
#include<bits/stdc++.h>
#define FOR(i,a,b) for (register int i=(a);i<=(b);i++)
#define For(i,a,b) for (register int i=(a);i>=(b);i--)
#define mem(i,j) memset(i,j,sizeof(i))
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+5;
int t,n,k,a[N],f[N],g[N];
vector <int> G[N],T[N];
int read()
{
	int x=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
int main()
{
//	freopen("data1.in","r",stdin);
	t=read();
	while (t--)
	{
		n=read(),k=read();
		FOR(i,0,n) G[i].clear(),f[i]=g[i]=0;
		FOR(i,1,k) T[i].clear();
		FOR(i,1,n) a[i]=read(),G[a[i]].pb(i);
		FOR(i,1,n) f[a[i]]++;
		For(i,n,0) g[i]=f[i]+g[i+1]/k;
		if (g[0]>=1)
		{
			int now=1,nowneed=1,need=1;
			FOR(i,1,n)
			{
				int len=G[i].size();
				FOR(j,0,len-1)
				{
					int pos=G[i][j];
					T[now].pb(pos);
					need--;
					if ((!need)&&now<k) now++,need=nowneed;
				}
				nowneed*=k,need*=k;
			}
			printf("1\n");
			FOR(i,1,k)
			{
				int len=T[i].size();
				printf("%d ",len);
				FOR(j,0,len-1) printf("%d ",T[i][j]);
				printf("\n");
			}
		}
		else printf("0\n");
	}
	return 0;
}

B. 小K与数据

原来并查集可以42分啊,我是傻逼吧我为什么不写并查集,又好写又有分
我写了一个 O ( n q ) O(nq) O(nq)的BFS期望得分22,但是我为什么这垃圾啊写了一个vis数组没有用上,我真是傻逼

正解

  • 小性质:对于一对区间,如果它们的重叠长度大于自身的一半,那么这两段区间的并集就是一段周期串,周期为 l 2 − l 1 l_2-l_1 l2l1
  • 我们对于每一对区间右区间的点向左边连边
  • 有了右区间不重叠的性质,那么每一个点只能出现在一个右区间中,那么每个点都这样向左连边形成的就是一个树结构,因为我们随机生成树的数据的时候就是这样做的
  • 那么我们每次插入或者查询一个位置的字符时候,就一直往左边跳,每次跳的时候在经过的区间上标记,最终把信息留在在某棵树的树根,然后每个区间的标记怎么打呢,我们利用第一个小性质,直接记录在模周期余几时可以取到该字符
  • 所以每次我们跳一次就必定跳出一个区间,区间最多n个,这样复杂度就对了, O ( n 2 ) O(n^2) O(n2)
考场代码

考场上没有写

if (!vis[...])

并查集是来赛后试了一发

#include<bits/stdc++.h>
#define FOR(i,a,b) for (register int i=(a);i<=(b);i++)
#define For(i,a,b) for (register int i=(a);i>=(b)i;--)
#define mem(i,j) memset(i,j,sizeof(i))
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,m1,m2,q,p[N],opt[N],a,b,c,d;
int fa[N];
int task1,task2;
char ch[N][2],s[N],ans[N];
//task2
int vis[N],l,r,line[N];
//
struct seg
{
	int l1,r1,l2,r2;
}f[N];
int read()
{
	int x=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
void Judge()
{
	task1=task2=0;
	if (n==m1) task1=1;
	if (n<=1000) task2=1;
	return;
}
void Task1()
{
	FOR(i,1,m1) s[p[i]]=ch[i][1];
	FOR(i,1,q) ans[i]=s[opt[i]];
	FOR(i,1,q) printf("%c\n",ans[i]);
	return;
}
void Task2()
{
	FOR(i,1,m1) s[p[i]]=ch[i][1];
	FOR(i,1,q)
	{
		FOR(j,1,n) vis[j]=0;
		r=0,l=1;
		line[++r]=opt[i];
		while (l<=r)
		{
			int tmp=line[l++];
			vis[tmp]++;
			if (s[tmp]!='?')
			{
				ans[i]=s[tmp];
				break;
			}
			FOR(j,1,m2)
			{
				if (f[j].l1<=tmp&&tmp<=f[j].r1) if (!vis[tmp-f[j].l1+f[j].l2]) line[++r]=tmp-f[j].l1+f[j].l2;
				if (f[j].l2<=tmp&&tmp<=f[j].r2) if (!vis[tmp-f[j].l2+f[j].l1]) line[++r]=tmp-f[j].l2+f[j].l1;
			}
		}
	}
	FOR(i,1,q) printf("%c\n",ans[i]);
	return;
}
int Getfa(int x)
{
	return (x==fa[x])?x:fa[x]=Getfa(fa[x]);
}
void Join(int x,int y)
{
	int fx=Getfa(x),fy=Getfa(y);
	if (fx!=fy) fa[fx]=fy;
	return;
}
int main()
{
//	freopen("data2.in","r",stdin);
	n=read(),m1=read(),m2=read(),q=read();
	FOR(i,1,n) fa[i]=i;
	FOR(i,1,m1) p[i]=read(),scanf("%s",ch[i]+1);
	FOR(i,1,m2) a=read(),b=read(),c=read(),d=read(),f[i]=(seg){a,b,c,d};
	FOR(i,1,q) opt[i]=read();
	FOR(i,1,m2) 
		FOR(j,1,f[i].r1-f[i].l1+1)
			Join(f[i].l1+j-1,f[i].l2+j-1);
	FOR(i,1,n) s[i]='?';
	FOR(i,1,m1) s[Getfa(p[i])]=ch[i][1];
	FOR(i,1,q) ans[i]=s[Getfa(opt[i])];
	FOR(i,1,q) printf("%c\n",ans[i]);
	return 0;
} 
赛后的代码
#include<bits/stdc++.h>
#define FOR(i,a,b) for (register int i=(a);i<=(b);i++)
#define For(i,a,b) for (register int i=(a);i>=(b);i--)
#define mem(i,j) memset(i,j,sizeof(i))
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1005;
int n,m1,m2,q,a,b,c,d,pos[N],p[N],opt[N];
char ch[N][2],ans[N];
map <int,char> mp;
struct seg
{
	int l1,r1,l2,r2,T;
	map <int,char> mp;
}f[N];
bool cmpr2(const seg s1,const seg s2)
{
	return s1.r2<s2.r2;
}
int read()
{
	int x=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
int Which_s2(int x)
{
	int s2=lower_bound(pos+1,pos+m2+1,x)-pos;
	if (s2<1||s2>m2||f[s2].l2>x) return -1;
	else return s2;
}
void Insert(int x,int cha)
{
	int s2=Which_s2(x);
	if (s2==-1)
	{
		mp[x]=cha;
		return;
	}
	f[s2].mp[x%f[s2].T]=cha;
	x=x-((x-f[s2].l1)/f[s2].T)*f[s2].T;
	Insert(x,cha);
	return;
}
char Query(int x)
{
	if (mp.count(x)) return mp[x];
	int s2=Which_s2(x);
	if (s2==-1)
	{
		if (mp.count(x)) return mp[x];
		else return '?';
	}
	if (f[s2].mp.count(x%f[s2].T)) return f[s2].mp[x%f[s2].T];
	x=x-((x-f[s2].l1)/f[s2].T)*f[s2].T;
	return Query(x);
}
int main()
{
//	freopen("data2.in","r",stdin);
	n=read(),m1=read(),m2=read(),q=read();
	FOR(i,1,m1) p[i]=read(),scanf("%s",ch[i]+1);
	FOR(i,1,m2) f[i].l1=read(),f[i].r1=read(),f[i].l2=read(),f[i].r2=read();
	FOR(i,1,q) opt[i]=read();
	sort(f+1,f+m2+1,cmpr2);
	FOR(i,1,m2)
	{
		pos[i]=f[i].r2;
		f[i].T=f[i].l2-f[i].l1;
	}
//	FOR(i,1,m2) printf("%d %d %d %d %d\n",f[i].l1,f[i].r1,f[i].l2,f[i].r2,f[i].T);
	FOR(i,1,m1) Insert(p[i],ch[i][1]);
	FOR(i,1,q) ans[i]=Query(opt[i]);
	FOR(i,1,q) printf("%c\n",ans[i]);
	return 0;
}

C. 小K与奇数

这题感觉暴力都不是很好写,十分的完全图就是每次每次四个四个地加点,新的四个点和原来的点构成二分图,它们之间把边跳完跳出一条路径,然后这新的四个点再按样例的方式连边

正解

  • 每一个点是且仅是一条路径的端点,所以每个点的度数必定是奇数,由于所有的路径长度都是偶数,所以边数必定是偶数,这样就判掉了无解的情况
  • 假设先忽略路径长度为偶数的限制,那么随机匹配 n 2 \frac{n}{2} 2n对点,连上虚边使它们的度数变成偶数,跑一个欧拉回路,虚边有 n 2 \frac{n}{2} 2n条于是刚好把这条欧拉回路断成了 n 2 \frac{n}{2} 2n符合题意条路径
  • 于是现在我们考虑用长度为2的边对原图做一个覆盖,使得每一条边被覆盖且仅被覆盖一次,这样我们得到了一个新图,旧边不看(两条旧边的编号贴在新边上),在新图上跑欧拉回路就把问题变成了上面那个
  • 我们仔细观察发现做完覆盖后每个点的度数奇偶性不会变,于是就过掉了
  • 还有就是怎么求这个覆盖,也就是怎么建出新图。DFS,对于每条不在DFS树上的非树边(它们必定全是返祖边,因为DFS),我们在它的上端点安排它的匹配,对于每一个点,将通向儿子的边尽可能互相匹配,这个DFS有个返回值,就是该点的父亲边有没有被匹配,这个东西在它的父节统计时有用,于是就过掉了
赛后代码
#include<bits/stdc++.h>
#define FOR(i,a,b) for (register int i=(a);i<=(b);i++)
#define pb push_back
#define mem(i,j) memset(i,j,sizeof(i))
using namespace std;
const int N=4e5+5;
int n,m,x,y,lim,du[N],not_good=0;
int vis[N],inq[N],used[N];
int first[N],nxxt[N<<1],tote=1;
int f[N],nxt[N<<1],tot=1;
vector <vector<int> > ans;
struct E
{
	int u,v,id1,id2;
}edge[N<<1],e[N<<1];
int read()
{
	int x=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
void Addedge(int u,int v)
{
	tote++;
	nxxt[tote]=first[u];
	first[u]=tote;
	edge[tote]=(E){u,v,0,0};
	return;
}
void Make_edge(int u,int v,int id1,int id2)
{
	tot++;
	nxt[tot]=f[u];
	f[u]=tot;
	e[tot]=(E){u,v,id1,id2};
	return;
}
void Pair_edge(int &cur,int j,int u)
{
	if (!cur)
	{
		cur=j;
		return;
	}
	int uu,vv;
	if (u==edge[cur].v) uu=edge[cur].u;
	else uu=edge[cur].v;
	if (u==edge[j].v) vv=edge[j].u;
	else vv=edge[j].v;
	Make_edge(uu,vv,cur>>1,j>>1);
	Make_edge(vv,uu,j>>1,cur>>1);
//	printf("%d %d %d %d %d %d\n",uu,vv,edge[cur].u,edge[cur].v,edge[j].u,edge[j].v);
	cur=0;
	return;
}
int DFS(int u,int pre_e)//父边匹配返回1 
{
	int cur=0;
	vis[u]=inq[u]=1;
	for (register int j=first[u];j!=-1;j=nxxt[j])
	{
		int v=edge[j].v;
		if (!vis[v])
		{
			if (!DFS(v,j)) Pair_edge(cur,j,u);
		}
		else if (!inq[v]) Pair_edge(cur,j,u);
	}
	inq[u]=0;
	if (!cur) return 0;//目前已经刚好匹配,父边未匹配 
	if (pre_e) Pair_edge(cur,pre_e,u);
	return 1;
}
void Euler(int u)
{
	for (register int j=f[u];j!=-1;j=nxt[j]) if (!used[j>>1])
	{
		used[j>>1]=1;
		int v=e[j].v;
		Euler(v);
		if (j>lim)
		{
			if (ans.empty()||(!ans.back().empty())) ans.pb(vector<int>());
		}
		else
		{
			ans.back().pb(e[j].id2);
			ans.back().pb(e[j].id1);
		}
	}
	return;
}
int main()
{
	mem(f,-1);
	mem(first,-1);
	n=read(),m=read();
	FOR(i,1,m) x=read(),y=read(),Addedge(x,y),Addedge(y,x),du[x]++,du[y]++;
	FOR(i,1,n) if (du[i]%2==0) not_good=1;
	if ((not_good)||(m%2==1)) {printf("0\n");exit(0);}
	FOR(i,1,n)
		if (!vis[i]) DFS(i,0);
	lim=tot;
	for (int i=1;i<n;i+=2) Make_edge(i,i+1,-1,-1),Make_edge(i+1,i,-1,-1);
	ans.pb(vector<int>());
	FOR(i,1,n) Euler(i);
	while (ans.back().empty()) ans.pop_back();
	printf("1\n");
	int len1=ans.size();
	FOR(i,0,len1-1)
	{
		int len2=ans[i].size();
		printf("%d ",len2);
		FOR(j,0,len2-1) printf("%d ",ans[i][j]);
		printf("\n");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值