前言
老年选手的智商训练(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'(1)
P′(1),又
P
(
x
)
=
A
(
x
)
1
−
B
(
x
)
P(x)={A(x)\over 1-B(x)}
P(x)=1−B(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=1n∑j=0ai−1(ji−1)
从大到小构造即可
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
咕咕咕