[300iq Contest 1]简要题解

前言

老年选手的智商训练(1/∞)
题目链接

Angle Beats

考虑建图,发现每个 ∗ * + + +的度数都为2,每个 . . .的度数都为1
对于每个 ∗ * + + +拆两个点,这两个点互相连边
对于一个 ∗ * 点,其中一个点向上/下的 . . .连边,另一个向左右的 . . .连边
对于一个 + + +点,两个点都向上下左右的 . . .连边
考虑这张图的最大匹配,可以发现,答案为match-cnt,cnt为 ∗ * + + +的数量
直接抄个带花树板子即可

Code

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=lst[a];i;i=nxt[i])
using namespace std;

const int N=2e4+5,M=N<<5;

int t[M],nxt[M],lst[N],l;
void add(int x,int y) {
	t[++l]=y;nxt[l]=lst[x];lst[x]=l;
	t[++l]=x;nxt[l]=lst[y];lst[y]=l;
}

int n,m,tot,fa[N],match[N],vis[N],pre[N],mark[N],an[N],used[N],id;
char s[105][105];
queue<int> q;

int Id(int x,int y) {return (x-1)*m+y;}

int get(int x) {return fa[x]!=x?fa[x]=get(fa[x]):x;}

int lca(int x,int y) {
	id++;x=get(x);y=get(y);
	while (vis[x]!=id) {
		vis[x]=id;
		x=get(pre[match[x]]);
		if (y) swap(x,y);
	}
	return x;
}

void merge(int x,int y) {fa[get(x)]=get(y);}

void flower(int x,int r) {
	for(;x!=r;) {
		int y=match[x],z=pre[y];
		if (get(z)!=r) pre[z]=y;
		if (mark[y]==2) q.push(y);mark[y]=1;
		if (mark[z]==2) q.push(z);mark[z]=1;
		merge(x,y);merge(y,z);
		x=z;
	}
}

void find(int S) {
	fo(i,1,tot) fa[i]=i,pre[i]=mark[i]=0;
	while (!q.empty()) q.pop();
	q.push(S);mark[S]=1;
	while (!q.empty()) {
		int x=q.front();q.pop();
		rep(i,x) {
			int y=t[i];
			if (get(y)==get(x)) continue;
			if (mark[y]==2) continue;
			if (!mark[y]) {
				mark[y]=2;pre[y]=x;
				if (!match[y]) {
					for(int z=y,tmp;z;z=tmp) tmp=match[pre[z]],match[z]=pre[z],match[pre[z]]=z;
					return;
				}
				mark[match[y]]=1;
				q.push(match[y]);
			} else {
				int z=lca(x,y);
				if (get(x)!=z) pre[x]=y;
				if (get(y)!=z) pre[y]=x;
				flower(x,z);
				flower(y,z);
			}
		}
	}
}

void fill(int p) {
	int x=(p-1)/m+1,y=(p-1)%m+1;
	if (x>1) used[an[Id(x-1,y)]]=id;
	if (x<n) used[an[Id(x+1,y)]]=id;
	if (y>1) used[an[Id(x,y-1)]]=id;
	if (y<m) used[an[Id(x,y+1)]]=id;
}

int main() {
	scanf("%d%d",&n,&m);
	fo(i,1,n) scanf("%s",s[i]+1);
	tot=n*m;
	fo(i,1,n)
		fo(j,1,m) {
			int x=Id(i,j);
			if (s[i][j]=='*') {
				int y=++tot;add(x,y);
				if (i>1&&s[i-1][j]=='.') add(x,Id(i-1,j));
				if (i<n&&s[i+1][j]=='.') add(x,Id(i+1,j));
				if (j>1&&s[i][j-1]=='.') add(y,Id(i,j-1));
				if (j<m&&s[i][j+1]=='.') add(y,Id(i,j+1));
			}
			if (s[i][j]=='+') {
				int y=++tot;add(x,y);
				if (i>1&&s[i-1][j]=='.') add(x,Id(i-1,j)),add(y,Id(i-1,j));
				if (i<n&&s[i+1][j]=='.') add(x,Id(i+1,j)),add(y,Id(i+1,j));
				if (j>1&&s[i][j-1]=='.') add(x,Id(i,j-1)),add(y,Id(i,j-1));
				if (j<m&&s[i][j+1]=='.') add(x,Id(i,j+1)),add(y,Id(i,j+1));
			}
		}
	fo(i,1,tot) if (!match[i]) find(i);
	fo(i,1,tot) fa[i]=i;
	int y=n*m;id=0;
	fo(i,1,n)
		fo(j,1,m)
			if (s[i][j]=='*'||s[i][j]=='+') {
				int x=Id(i,j);y++;
				if (match[x]!=y&&match[x]&&match[y]) {
					id++;
					fill(x);fill(match[x]);fill(match[y]);
					int ret=1;while (used[ret]==id) ret++;
					an[x]=an[match[x]]=an[match[y]]=ret;
				} 
			}
	fo(i,1,n) {
		fo(j,1,m) {
			int x=Id(i,j);
			putchar(an[x]?'a'+an[x]-1:s[i][j]);
		}
		puts("");
	}
	return 0;
}

Best Subsequence

这东西是一个环
显然最小值一定会取(证明反证一下
那么我们把最小值shift到开头,二分答案,后面的设个Dp[i]表示以i结尾最多能取多少个
用线段树维护即可

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=2e5+5;

int n,k,w[N],a[N],b[N],id[N],o;
int tr[N<<2];

void Ins(int v,int l,int r,int x,int y) {
	tr[v]=max(tr[v],y);
	if (l==r) return;
	int mid=l+r>>1;
	if (x<=mid) Ins(v<<1,l,mid,x,y);
	else Ins(v<<1|1,mid+1,r,x,y);
}

int Que(int v,int l,int r,int x,int y) {
	if (x>y) return 0;
	if (x<=l&&r<=y) return tr[v];
	int mid=l+r>>1,tmp=0;
	if (x<=mid) tmp=max(tmp,Que(v<<1,l,mid,x,y));
	if (y>mid) tmp=max(tmp,Que(v<<1|1,mid+1,r,x,y));
	return tmp;
}

int Id(int x) {
	if (x<b[1]) return 0;
	if (x>=b[o]) return o;
	return upper_bound(b+1,b+o+1,x)-b-1;
}

int check(int x) {
	if (a[1]>=x) return 0;
	fo(i,1,o<<2) tr[i]=0;
	Ins(1,1,o,1,1);
	int ret=0;
	fo(i,2,n) {
		if (a[i]>=x) continue;
		int now=Que(1,1,o,1,Id(x-a[i]))+1;
		if (a[i]+a[1]<=x) ret=max(ret,now);
		Ins(1,1,o,id[i],now);
	}
	return ret;
}

int main() {
	n=read();k=read();
	int p=0,mn=1e9+5;
	fo(i,1,n) {
		w[i]=read();
		if (w[i]<mn) mn=w[i],p=i;
	}
	fo(i,p,n) a[++a[0]]=w[i];
	fo(i,1,p-1) a[++a[0]]=w[i];

	fo(i,1,n) b[i]=a[i];sort(b+1,b+n+1);
	o=unique(b+1,b+n+1)-b-1;
	fo(i,1,n) id[i]=Id(a[i]);

	ll l=1,r=2e9,ans=0;
	while (l<=r) {
		ll mid=l+r>>1;
		if (check(mid)>=k) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld\n",ans);
	return 0;
}

Cool Pairs

考虑令a取遍-n~-1
对于每个b[q[i]],其对答案的贡献为[0,q[i])
那么我们不如取一个点x,使得x之前的b[q[i]]的贡献为q[i]-1,x把k补齐,其余贡献为0
构造方法显然

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

ll read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	ll x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=3e5+5;

int n,a[N],b[N],c[N];
ll k;

int main() {
	n=read();k=read();
	fo(i,1,n) a[read()]=-n+i-1;
	fo(i,1,n) {
		int x=read();
		if (!k) b[x]=n;
		else if (x-1<=k) k-=x-1;
		else {
			fo(j,1,x-1) c[j]=a[j];
			nth_element(c+1,c+k+1,c+x);
			b[x]=-c[k+1];k=0;
		}
	}
	puts("Yes");
	fo(i,1,n) printf("%d ",a[i]);puts("");
	fo(i,1,n) printf("%d ",b[i]);puts("");
	return 0;
}

Date

显然把所有区间按权值从大到小排序,能取就取
然后就是原题不解释
直接抄过来改一改就过了

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=3e5+5;
const ll inf=1e18;

int n,m,c[N],cnt[N],id[N],rk[N];
ll a[N],mx[N<<2],mn[N<<2],tmx[N<<2],tmn[N<<2];

void add(int x,ll z,int opt) {
	if (opt==0) mx[x]+=z,tmx[x]+=z;
	if (opt==1) mn[x]+=z,tmn[x]+=z;
}

void down(int v) {
	if (tmx[v]) {
		add(v<<1,tmx[v],0);
		add(v<<1|1,tmx[v],0);
		tmx[v]=0;
	}
	if (tmn[v]) {
		add(v<<1,tmn[v],1);
		add(v<<1|1,tmn[v],1);
		tmn[v]=0;
	}
}

void update(int v) {
	mx[v]=max(mx[v<<1],mx[v<<1|1]);
	mn[v]=min(mn[v<<1],mn[v<<1|1]);
}

void modify(int v,int l,int r,int x,int y,ll z,int opt) {
	if (x>y) return;
	if (x<=l&&r<=y) {add(v,z,opt);return;}
	int mid=l+r>>1;down(v);
	if (x<=mid) modify(v<<1,l,mid,x,y,z,opt);
	if (y>mid) modify(v<<1|1,mid+1,r,x,y,z,opt);
	update(v);
}

ll query_min(int v,int l,int r,int x,int y) {
	if (x<=l&&r<=y) return mn[v];
	int mid=l+r>>1;ll tmp=inf;down(v);
	if (x<=mid) tmp=min(tmp,query_min(v<<1,l,mid,x,y));
	if (y>mid) tmp=min(tmp,query_min(v<<1|1,mid+1,r,x,y));
	return tmp;
}

ll query_max(int v,int l,int r,int x,int y) {
	if (x<=l&&r<=y) return mx[v];
	int mid=l+r>>1;ll tmp=-inf;down(v);
	if (x<=mid) tmp=max(tmp,query_max(v<<1,l,mid,x,y));
	if (y>mid) tmp=max(tmp,query_max(v<<1|1,mid+1,r,x,y));
	return tmp;
}

struct Q{int l,r,k,id;}q[N];
bool cmp(Q a,Q b) {return a.k>b.k;}

void init() {
	m=read();n=read();
	fo(i,1,n) a[i]=read();
	fo(i,1,m) q[i].l=read(),q[i].r=read(),q[i].k=read(),q[i].id=i;
	sort(q+1,q+m+1,cmp);
	fo(i,1,m) rk[i]=q[i].id;
}

void prepare() {
	fo(i,1,m) {
		cnt[q[i].l]++;
		cnt[q[i].r+1]--;
	}
	fo(i,1,n) cnt[i]+=cnt[i-1];
	int Id=0;
	fo(i,1,n) if (cnt[i]) id[i]=++Id,c[Id]=a[i];
	fo(i,1,m) q[i].l=id[q[i].l],q[i].r=id[q[i].r];
	n=Id;fo(i,1,n) a[i]=c[i];
	fo(i,1,n) a[i]+=a[i-1];
}

int main() {
	init();prepare();
	fo(i,1,m) {
		modify(1,1,m,rk[i],rk[i],-a[q[i].r],0);
		modify(1,1,m,rk[i],rk[i],-a[q[i].l-1],1);
	}
	ll ans=0;
	fo(i,1,m) {
		int t=rk[i];
		ll x=query_min(1,1,m,1,t)-query_max(1,1,m,t,m);
		x=min(x,1ll);ans+=x*q[i].k;
		modify(1,1,m,rk[i],m,x,0);
		modify(1,1,m,rk[i]+1,m,x,1);
	}
	printf("%lld\n",ans);
	return 0;
}

Expected Value

平面图的边数<=3n-6
走k步到的概率是是一个n阶递推,先求出前2n项,用BM求出递推式
然后答案可以写成 P ′ ( 1 ) P&#x27;(1) P(1),又 P ( x ) = A ( x ) 1 − B ( x ) P(x)={A(x)\over 1-B(x)} P(x)=1B(x)A(x)
直接暴力求导
抄个板子

Code

#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=lst[a];i;i=nxt[i])
#define pb(a) push_back(a)
using namespace std;

typedef long long ll;

const int N=6e3+5,Mo=998244353;

int pwr(int x,int y) {
	int z=1;
	for(;y;y>>=1,x=(ll)x*x%Mo)
		if (y&1) z=(ll)z*x%Mo;
	return z;
}

vector<int> to[N];
void add(int x,int y) {to[x].pb(y);}

int n,m,ty,deg[N],f[N],g[N],p[N],q[N],inv[N],x,y;
vector<int> h;

void inc(int &x,int y) {x=x+y>=Mo?x+y-Mo:x+y;}

namespace BM{
	vector<int> h[N];
	int cnt,fail[N],d[N],mx;
	vector<int> work(int n,int *a) {
		h[cnt=mx=0].clear();
		fo(i,1,n) {
			int now=-a[i];
			for(unsigned j=0;j<h[cnt].size();j++) (now+=(ll)a[i-j-1]*h[cnt][j]%Mo)%=Mo;
			d[i]=now;if (!now) continue;
			fail[cnt]=i;
			if (!cnt) {
				h[++cnt].clear();
				h[cnt].resize(i);
				continue;
			}
			vector<int> r;r.resize(i-fail[mx]-1);
			int mul=-(ll)now*pwr(d[fail[mx]],Mo-2)%Mo;
			r.pb(-mul);for(unsigned j=0;j<h[mx].size();j++) r.pb((ll)h[mx][j]*mul%Mo);
			if (r.size()<h[cnt].size()) r.resize(h[cnt].size());
			for(unsigned j=0;j<h[cnt].size();j++) (r[j]+=h[cnt][j])%=Mo;
			if (i-fail[mx]+h[mx].size()>=h[cnt].size()) mx=cnt;
			h[++cnt]=r;
		}
		return h[cnt];
	}
}

int main() {
	scanf("%d",&n);
	fo(i,1,n) deg[i]=0,to[i].clear();
	fo(i,1,n) scanf("%d%d",&x,&y);
	scanf("%d",&m);
	fo(i,1,m) {
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
		deg[x]++;deg[y]++;
	}
	fo(i,1,n) inv[i]=pwr(deg[i],Mo-2);
	fo(i,1,n) f[i]=0;f[1]=1;p[0]=0;
	fo(i,1,n*2) {
		memset(g+1,0,n*4);
		fo(j,1,n) {
			int now=(ll)f[j]*inv[j]%Mo;
			for(int k:to[j]) inc(g[k],now);
		}
		memcpy(f+1,g+1,n*4);
		p[i]=f[n];f[n]=0;
	}
	h=BM::work(n*2,p);
	int m=h.size();
	fo(i,1,n*2) q[i]=0;
	fo(i,0,m-1) fo(j,0,m-i-1) (q[i+j+1]+=(ll)p[i]*h[j]%Mo)%=Mo;
	fo(i,1,m) q[i]=(p[i]-q[i])%Mo;
	int a=0,da=0,b=1,db=0;
	fo(i,1,m) {
		(a+=q[i])%=Mo,(da+=(ll)q[i]*i%Mo)%=Mo;
		(b-=h[i-1])%=Mo,(db-=(ll)h[i-1]*i%Mo)%=Mo;
	}
	int ans=((ll)da*b%Mo-(ll)db*a%Mo)%Mo;
	ans=(ll)ans*pwr((ll)b*b%Mo,Mo-2)%Mo;
	printf("%d\n",(ans+Mo)%Mo);
	return 0;
}

Free Edges

操作相当于每次删去度数为1的点
显然条件为无环
直接数连通块数

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=1e5+5;

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

int get(int x) {return f[x]?f[x]=get(f[x]):x;}

void merge(int x,int y) {
	x=get(x);y=get(y);
	if (x==y) return;
	f[y]=x;k++;
}

int main() {
	n=read();m=read();
	fo(i,1,m) {
		int x=read(),y=read();
		merge(x,y);
	}
	printf("%d\n",m-k);
	return 0;
}

Graph Counting

题解告诉我们,一个图合法的充要条件为:
存在x个点和其余所有点连边,并且删去这x个点,剩余的图为x+2个奇团
必要性很好证,充分性不会QwQ
考虑枚举x,剩余的相当于把2n-x分成x+2个奇数
相当于把2n-2x-2分成x+2个偶数
相当于把n-x-1分成x+2个非负整数
相当于把n+1分成x+2个正整数
考虑对所有x重合,相当于将n+1分成>=2个正整数
显然是P(n+1)-1
用五边形数求分拆数即可
exp没跑过去太真实了

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

const int N=5e5+5,Mo=998244353;

void inc(int &x,int y) {x=x+y>=Mo?x+y-Mo:x+y;}

int n,f[N];

int Five(int x) {return x*(3*x-1)/2;}

int main() {
	scanf("%d",&n);n++;
	f[0]=f[1]=1;
	fo(i,2,n) {
		for(int j=1;Five(j)<=i;j++) {
			inc(f[i],(j&1)?f[i-Five(j)]:Mo-f[i-Five(j)]);
			if (Five(-j)<=i) inc(f[i],(j&1)?f[i-Five(-j)]:Mo-f[i-Five(-j)]);
		}
	}
	printf("%d\n",f[n]-1);
	return 0;
}

Hall’s Theorem

某套SD集训的题的A
考虑左边的每个点都向右边的每个点的一个前缀连边
设i向前ai个点连边,把ai从小到大排序
考虑好的集合的数量,容易发现为 ∑ i = 1 n ∑ j = 0 a i − 1 ( i − 1 j ) \sum_{i=1}^{n}\sum_{j=0}^{ai-1}\binom{i-1}{j} i=1nj=0ai1(ji1)
从大到小构造即可
n这么小是因为SPJ只能做2^n…

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

const int N=35;

int e[N][N],n,k,C[N][N],a[N*N][2],tot;

int main() {
	scanf("%d%d",&n,&k);
	fo(i,0,n) {
		C[i][0]=1;
		fo(j,1,i) C[i][j]=C[i-1][j]+C[i-1][j-1];
	}
	fo(i,0,n) fo(j,1,n) C[i][j]+=C[i][j-1];
	k=(1<<n)-k-1;
	int j=n;
	fd(i,n,1) {
		while (j>=1&&C[i-1][j-1]>k) j--;
		fo(w,1,j) {
			++tot;
			a[tot][0]=i;a[tot][1]=w;
		}
		k-=C[i-1][j-1];
	}
	printf("%d\n",tot);
	fo(i,1,tot) printf("%d %d\n",a[i][0],a[i][1]);
	return 0;
}

Interesting Graph

选出的集合必须保证至少存在两个点不连通否则GG
=>连通块大小<=6
对每个连通块暴力,然后分治FFT+多点求值
6个点的本质不同的图很少
可以对每种图求出数量最后直接求∏(P(x))^k

Code

咕咕咕

Jealous Split

题解告诉我们:选出平方和最小的划分方法,这样一定合法
考虑证明,如果相邻两段和为a,b,x是a最右边的值或者b最左边的值
如果a-b<=x则显然合法
若a-b>x我们可以变成a-x,b+x,那么平方和会变小
_ (:з」∠) _
凸优化+斜率优化即可

Code

咕咕咕

Knowledge

题解告诉我们这个是"The presentation of group of Tetrahedral symmetry"…
看不懂没关系,考虑另一个东西
手玩发现对于每个串可以通过若干次操作得到一个唯一的最小的串
这个最小的串不会很多,直接矩乘

Code

咕咕咕

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值