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\) 个点合法的答案就是:

\[ans=\sum_{i=0}^{k} (-1)^i f_{k,i} \]

但是你说得对 \(f_{k,i}\) 咋求啊。

注意到我们的条件是 或。

也就是如果一个点不合法,它 \(x,y\) 轴上全不合法。

于是我们把 \(x,y\) 分开,设 \(fx_{k,i},fy_{k,i}\) 分别表示 \(x,y\) 轴上各选了 \(k\) 个点,至少 \(i\) 个下标不合法的状态数。

这个不合法的下标的意思是 \(p\) 位置被选了,\(p-1,p+1\) 没被选,也就是仅在这个轴上不合法的情况。

然后我们可以把这些不合法的组组合合成点,也就是:

\[f_{k,i}=fx_{k,i}\times fy_{k,i} \times (k-i)!\times (i)! \]

后面两个阶乘的意义是:我们可以把已知一定不合法的 \(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\) 的定义是 至少

那么这就是一个插板法,方案数为:

\[\binom{n-2i}{n-k-i} \]

之后要把 \(i\)\(2i\) 段插回去吧,我们有 \(n-k-i\) 个位置可以插,但是两个长为 \(2\)可以挨在一起,于是是可以有 \(0\) 的插板法,于是方案数是:

\[\binom{n-k+1}{i} \]

乘起来就是:

\[fx_{k,i}=\binom{n-2i}{n-k-i}\binom{n-k+1}{i} \]

这样我们已经能 \(O(n)\) 求一个 \(k\) 了。但其实可以 \(O(1)\) 求的,推式子要用高科技,于是口胡先到这。

T4

放了。

基础?算法选讲

CSP-S 2019 划分

[USACO16DEC] Robotic Cow Herd P

[USACO24OPEN] Identity Theft P

下面两个问号题。

ARC138D

ABC288G

posted @ 2025-11-20 21:37  hm2ns  阅读(32)  评论(0)    收藏  举报