OI 笑传 #31
今天是 bct Day1,赛时 \(100+0+16+0=116\),rk.21。
评价是猎奇场,赛时比较法座,去写 B 没写完,sheep 神 \(100+100+0+0=200\),rk.5,让我们膜拜/bx
T1
依然大胆猜结论,首先建系,把下标-排列状物放到坐标系里面,然后手摸一下就会发现好像只有横着竖着差着 \(1\) 格的两个点才会连上边。
然后我们想想差着 ? 个格应该有个上界,发现 \(N\le 5\times 10^4\),所以我认为上界是 \(\sqrt{N}\) 格合理吧。
其实开 \(100\) 也能过。
然后边数是 \(10^7\) 量级,但是不慌因为边权也是 \(10^7\) 量级,桶排即可。
赛时先写了遍 sort 的,然后大样例过了改的桶。
然后卡常卡常。
线上测在 SDOISUOJ 上还是被卡常挂了 \(15\) 分,然后成为最晚知道 SDOI 机子慢的人之一。
线下测是可过的。
code
Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
i64 x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=1e5+5;
const int M=1e7+5;
const int CM=5e6+6;
int p[N];
int xarr[N];
int yarr[N];
struct edge{
int u;
int v;
int w;
}e[M];
bool cmp(edge x,edge y){
return x.w<y.w;
}
struct ccc{
int v;int w;
}p1[M];
int tong[CM];
int fa[N];
int _find(int u){
return fa[u]==u?u:fa[u]=_find(fa[u]);
}
int main(){
int n;cin>>n;
for(int i=1;i<=CM-2;i++){tong[i]=-1;}
for(int i=1;i<=n;i++){
p[i]=rd;
}
if(n<=1000){
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(i==j)continue;
e[++cnt].u=i;
e[cnt].v=j;
e[cnt].w=abs(e[cnt].u-e[cnt].v)*abs(p[e[cnt].u]-p[e[cnt].v]);
}
}
for(int i=1;i<=n;i++){
fa[i]=i;
}
sort(e+1,e+1+cnt,cmp);
int cmg=0;
i64 ans=0;
for(int i=1;i<=cnt;i++){
int t1=_find(e[i].u);
int t2=_find(e[i].v);
if(t1!=t2){
fa[t1]=t2;
ans=ans+e[i].w;
cmg++;
if(cmg==n-1)break;
}
}
cout<<ans;
return 0;
}
else{
for(int i=1;i<=n;i++){
xarr[i]=i;
yarr[p[i]]=i;
}
int tc=min((int)sqrt(n),100);
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=min(n,i+tc);j++){
e[++cnt].u=i;
e[cnt].v=xarr[j];
e[cnt].w=abs(e[cnt].u-e[cnt].v)*abs(p[e[cnt].u]-p[e[cnt].v]);
p1[cnt].v=cnt;
p1[cnt].w=tong[e[cnt].w];
tong[e[cnt].w]=cnt;
}
for(int j=p[i]+1;j<=min(n,p[i]+tc);j++){
e[++cnt].u=yarr[p[i]];
e[cnt].v=yarr[j];
e[cnt].w=abs(e[cnt].u-e[cnt].v)*abs(p[e[cnt].u]-p[e[cnt].v]);
p1[cnt].v=cnt;
p1[cnt].w=tong[e[cnt].w];
tong[e[cnt].w]=cnt;
}
}
for(int i=1;i<=n;i++){
fa[i]=i;
}
i64 ans=0;
int cmg=0;
for(int i=1;i<=M;i++){
if(tong[i]==-1)continue;
for(int j=tong[i];j!=-1;j=p1[j].w){
int t1=_find(e[p1[j].v].u);
int t2=_find(e[p1[j].v].v);
if(t1!=t2){
fa[t1]=t2;
ans=ans+e[p1[j].v].w;
cmg++;
if(cmg==n-1)break;
}
}
if(cmg==n-1)break;
}
cout<<ans;
}
return 0;
}
T2
DP 题,赛时发现状态巨恶心然后就去写 \(A,B,C \le 99\) 的暴力,具体就是我又猜这样 \(C'\) 的长度 \(\le 5\),于是先用 DFS 把 \(A',B',C'\) 带上填入的未定义字符放进去,所有情况搜出来然后再 \(C' A' B'\) 排排坐,从低位到高位处理,看看会不会进位,是不是都填上等等等等。
然后发现这种东西写的也巨恶心。
DP 是一个类似编辑距离状物,考虑构造 \(C'\) 同时构造 \(A',B'\),让 \(f_{p,i,j,k,c,S}\) 表示从低位到高位构造了 \(C'\) 的 \(p\) 位,用的是 \(A,B,C\) 的 \(i,j,k\) 位。上一位有没有进位 \(0/1\),\(A,B,C\) 有没有用完,是一个 3bit 的 mask。
DP 时枚举 \(C'\) 的 \(p\) 位应该是什么数 \(0\sim 9\)。然后枚举这一位的 \(i,j,k\),要不要用 \(i+1,j+1,k+1\),于是要套七个 for。
然后如果填 \(A',B'\) 的加起来要进位/可能进位就要把 c 也考虑上。再来一个 for。
然后还要考虑我们要不要立刻终止使用 \(A,B,C\),舍弃后面所有的数位,然后又是三个 for。算上状态那再来一个。
综合是十二层 for,由于是口胡可能还少了什么来着,sheep 写了十五层 for。
sheep: 这个东西打破了我嵌套 for 循环的层数记录,虽然我过几天可能就忘了这个记录了。
这个 \(p\) 也是有上界的,因为我们无论如何都可以轮流在 \(A,B\) 里插入数来让 \(A+B\) 不超过 \(20\) 位,并且与当前的 \(C\) 符合。
口胡到这里。
T3
打表的 16pts 暴力
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int ans[10][10][10];
int main(){
freopen("society.in","r",stdin);
freopen("society.out","w",stdout);
ans[1][1][0]=0;
ans[1][1][1]=0;
ans[1][2][0]=0;
ans[1][2][1]=0;
ans[1][3][0]=0;
ans[1][3][1]=0;
ans[1][4][0]=0;
ans[1][4][1]=0;
ans[1][5][0]=0;
ans[1][5][1]=0;
ans[2][1][0]=0;
ans[2][1][1]=0;
ans[2][2][0]=0;
ans[2][2][1]=0;
ans[2][2][2]=2;
ans[2][3][0]=0;
ans[2][3][1]=0;
ans[2][3][2]=6;
ans[2][4][0]=0;
ans[2][4][1]=0;
ans[2][4][2]=12;
ans[2][5][0]=0;
ans[2][5][1]=0;
ans[2][5][2]=20;
ans[3][1][0]=0;
ans[3][1][1]=0;
ans[3][2][0]=0;
ans[3][2][1]=0;
ans[3][2][2]=6;
ans[3][3][0]=0;
ans[3][3][1]=0;
ans[3][3][2]=16;
ans[3][3][3]=6;
ans[3][4][0]=0;
ans[3][4][1]=0;
ans[3][4][2]=30;
ans[3][4][3]=24;
ans[3][5][0]=0;
ans[3][5][1]=0;
ans[3][5][2]=48;
ans[3][5][3]=60;
ans[4][1][0]=0;
ans[4][1][1]=0;
ans[4][2][0]=0;
ans[4][2][1]=0;
ans[4][2][2]=12;
ans[4][3][0]=0;
ans[4][3][1]=0;
ans[4][3][2]=30;
ans[4][3][3]=24;
ans[4][4][0]=0;
ans[4][4][1]=0;
ans[4][4][2]=54;
ans[4][4][3]=88;
ans[4][4][4]=24;
ans[4][5][0]=0;
ans[4][5][1]=0;
ans[4][5][2]=84;
ans[4][5][3]=204;
ans[4][5][4]=120;
ans[5][1][0]=0;
ans[5][1][1]=0;
ans[5][2][0]=0;
ans[5][2][1]=0;
ans[5][2][2]=20;
ans[5][3][0]=0;
ans[5][3][1]=0;
ans[5][3][2]=48;
ans[5][3][3]=60;
ans[5][4][0]=0;
ans[5][4][1]=0;
ans[5][4][2]=84;
ans[5][4][3]=204;
ans[5][4][4]=120;
ans[5][5][0]=0;
ans[5][5][1]=0;
ans[5][5][2]=128;
ans[5][5][3]=450;
ans[5][5][4]=576;
ans[5][5][5]=120;
int n,m,q;cin>>n>>m>>q;
for(int i=1;i<=q;i++){
int k;cin>>k;
cout<<ans[n][m][k]<<' ';
}
return 0;
}
只说关键的转化,后面推式子就不说了。把握不住。
首先容斥,我们考虑一个标准的容斥 \(f_{k,i}\) 表示选出来的 \(k\) 个点里面至少有 \(i\) 个点不合法的情况数。
于是对于选出来 \(k\) 个点合法的答案就是:
但是你说得对 \(f_{k,i}\) 咋求啊。
注意到我们的条件是 或。
也就是如果一个点不合法,它 \(x,y\) 轴上全不合法。
于是我们把 \(x,y\) 分开,设 \(fx_{k,i},fy_{k,i}\) 分别表示 \(x,y\) 轴上各选了 \(k\) 个点,至少 \(i\) 个下标不合法的状态数。
这个不合法的下标的意思是 \(p\) 位置被选了,\(p-1,p+1\) 没被选,也就是仅在这个轴上不合法的情况。
然后我们可以把这些不合法的组组合合成点,也就是:
后面两个阶乘的意义是:我们可以把已知一定不合法的 \(i\) 个点组合出 \(i!\) 个一定不合法的情况,也就是固定一边一边全排列。
同样的,我们也能把其它未知点这么 \((k-i)!\) 排出来,之后乘法原理乘起来即可。
接下来要想想怎么求出 \(fx_{k,i},fy_{k,i}\),让我们以 \(fx\) 为例,设 \(x\) 轴上一共有 \(n\) 个点。
考虑这个东西的组合意义。选出来不合法的点是个独立的点,两边都没选。
于是我们把点连成线段,线段都是极长的。
为了方便计数,定义一个极长的线段加上它后面的一个空位置组成一个段,这样为了我们能选到最后一个位置,我们给 \(n+1\) 单独开一个空位置。
请注意下文线段和段两个名词的使用。
接下来,选出来不合法的点一定就是一个长为 \(2\) 的段。不选的且不在线段末尾的点是一个长度为 \(1\) 的段。
我们把这三个东西都表示成了一个统一的东西,接下来回到我们的计数。
枚举 \(i\),那么轴里一定有 \(2i\) 个位置被占掉。
剩下 \(n-2i+1\) 个位置,要把这些位置分成长度不为 \(0\) 的线段,线段的数量为 \(n-k-i+1\)。这个手摸一下就行。减 \(k\) 的意思就是放掉我们段后面加的那个空位置。
注意我们无需避免出现长度为 \(1\) 的线段,因为我们 \(i\) 的定义是 至少。
那么这就是一个插板法,方案数为:
之后要把 \(i\) 个 \(2i\) 段插回去吧,我们有 \(n-k-i\) 个位置可以插,但是两个长为 \(2\) 的段可以挨在一起,于是是可以有 \(0\) 的插板法,于是方案数是:
乘起来就是:
这样我们已经能 \(O(n)\) 求一个 \(k\) 了。但其实可以 \(O(1)\) 求的,推式子要用高科技,于是口胡先到这。
T4
放了。
基础?算法选讲
CSP-S 2019 划分
[USACO16DEC] Robotic Cow Herd P
[USACO24OPEN] Identity Theft P
下面两个问号题。

浙公网安备 33010602011771号