20230828比赛总结

分数

预估分数: 100 + 100 + 100 + 0 = 300 100+100+100+0=300 100+100+100+0=300
实际分数: 100 + 60 + 100 + 0 = 260 100+60+100+0=260 100+60+100+0=260

反思

做得挺顺的
感觉不需要检查 50 m i n 50min 50min

B

考场降智,可以用一个简单的树状数组维护,居然写了主席树!!!还 T L E TLE TLE

题解

比赛链接

A

直接用期望的线性性做即可

B

考虑二分,然后对正负分类讨论,用树状数组维护即可

C

题目的限制 i − > a i i->a_i i>ai 建边后限制即为环的长度 = 1 =1 =1 2 2 2
考虑枚举 k k k 个数那些排列后不在前 k k k 个中
然后发现前 k k k 个数中一些数只能跟后面的匹配,一些数内部自己匹配
可以预处理出 n n n 个点内部匹配的方案数,然后用组合数计算一下答案即可
具体细节不细说,注意要高精

#include 
using namespace std;
const int N=210;
int n,k,a[N],b[N];
bool vis[N];
vector<int> ways[N],C[N][N],ans;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
vector<int> les(vector<int> A){
    for(int i=0;i<A.size()-1;i++){
		int t=A[i]/10;
		A[i+1]+=t,A[i]-=t*10;
	}
	return A;
}
vector<int> expand(vector<int> A){
	int len=A.size()-1;
	while(A[len]>=10){
		int t=A[len]/10;A[len]-=t*10;
		A.push_back(t),len++;
	}
	return A;
}
vector<int> mul(vector<int> A,int mul){
    for(int i=0;i<A.size();i++) A[i]*=mul;
	return expand(les(A));
}
vector<int> add(vector<int> A,vector<int> B){
	if(A.size()<B.size()) swap(A,B);
	for(int i=0;i<B.size();i++) A[i]+=B[i];
	return expand(les(A));
}
vector<int> MUL(vector<int> A,vector<int> B){
	vector<int> C;C.clear();
	for(int i=0;i<A.size()+B.size()-1;i++) C.push_back(0);
	for(int i=0;i<A.size();i++) for(int j=0;j<B.size();j++) C[i+j]+=A[i]*B[j];
	return expand(les(C));
}
int main(){
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
	n=read(),k=read();
	for(int i=1;i<=k;i++) b[i]=read();
    ways[0].push_back(1),ways[1].push_back(1);
	for(int i=2;i<=n-k;i++){
        ways[i]=mul(ways[i-2],i-1);
		ways[i]=add(ways[i],ways[i-1]);
    }
    // for(int i=1;i<=n-k;i++){
    //     for(int j=ways[i].size()-1;j>=0;j--) cout<
    //     cout<<'\n';
    // }
	C[0][0].push_back(1);
	for(int i=1;i<=n-k;i++) for(int j=0;j<=i;j++) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
	// for(int i=1;i<=n-k;i++) for(int j=0;j<=i;j++){
    //     for(int k=C[i][j].size()-1;k>=0;k--) cout<
    //     cout<<'\n';
    // }
	ans.push_back(0);
    for(int i=0;i<=k;i++){//1-i属于1-k一段
		if(n-k<k-i) continue;
        for(int j=1;j<=n;j++) vis[j]=0;
		for(int j=i+1;j<=k;j++) vis[b[j]]=1;
		for(int j=1;j<=n;j++) a[j]=0;
		int jj=0;
		for(int j=1;j<=k;j++) if(!vis[j]) a[j]=b[++jj];
		bool flg=1;
		for(int j=1;j<=k;j++) if(!vis[j]) if(a[a[j]]!=j){ flg=0;break;}
        // cout<
        if(flg) ans=add(ans,MUL(C[n-k][k-i],ways[n-k-(k-i)]));
	}
	for(int i=ans.size()-1;i>=0;i--) putchar(ans[i]+48);
	return 0;
}

D

n < = 3000 n<=3000 n<=3000 给暴力的 n 2    d p n^2\;dp n2dp
第一感觉想到一个贪心做法:每次取出一个深度最深的未删过的点 u u u,然后把 u u u u u u k − 1 k-1 k1 级祖先全部删去
考虑到这个贪心做法有另一种易做的形式:在一个大根堆(按照深度)中加入所有的叶子,然后取出堆顶,如果没有被删去,就标记,然后加入这个点的 k k k 级祖先
显然,这样贪心是对的
考虑这样贪心的时间复杂度
结论:将长度限制为 k k k 时,答案不会超过 O ( L + n − L k ) O(L+\frac{n-L}{k}) O(L+knL),其中 L L L 为叶子个数
证明:暂时不会
我们可以令 d i d_i di i i i 到最近的叶子的距离,然后对于每个 k k k 加入 d i = k d_i=k di=k 进入初始堆即可,因为叶子无论如何都必须选,所以提前全部加入贡献
考虑如何快速判断一个点是否被覆盖,即一个点的子树中是否有距离它小于 k k k 的点,线段树维护 d f s dfs dfs 序即可
还需要倍增技巧求 k k k 级祖先(这里倍增不是复杂度瓶颈,所以不必换成长剖)
时间复杂度为 O ( ∑ n k l o g n k ) O(\sum\frac{n}{k}log\frac{n}{k}) O(knlogkn),是调和级数,即为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include 
using namespace std;
typedef pair<int,int> pii;
const int N=100100;
int n,totleaf,ans[N],depth[N],dis[N],up[N][20];
int dfn[N],dfs_clock,siz[N];
int e[N<<1],ne[N<<1],h[N],idx;
bool isleaf[N];
vector<int> vecd[N];
priority_queue<pii> que;
int seg[N<<2];
int used[N],cnt;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
void dfs(int u,int fa){
	depth[u]=depth[fa]+1,isleaf[u]=1,dis[u]=1e9;
	dfn[u]=++dfs_clock,siz[u]=1;
	for(int i=h[u];~i;i=ne[i])
		if(e[i]!=fa){
			isleaf[u]=0,dfs(e[i],u),up[e[i]][0]=u;
			dis[u]=min(dis[u],dis[e[i]]+1),siz[u]+=siz[e[i]];
		}
	if(isleaf[u]) dis[u]=0;
	totleaf+=isleaf[u];
}
void modify(int l,int r,int x,int pos,int val){
	if(l==r){ seg[x]=val;return;}
	int mid=(l+r)>>1;
	if(mid>=pos) modify(l,mid,x<<1,pos,val);
	else modify(mid+1,r,x<<1^1,pos,val);
	seg[x]=min(seg[x<<1],seg[x<<1^1]);
}
int query(int l,int r,int x,int L,int R){
	if(L<=l&&r<=R) return seg[x];
	int mid=(l+r)>>1;
	if(mid>=L&&mid<R) return min(query(l,mid,x<<1,L,R),query(mid+1,r,x<<1^1,L,R));
	if(mid>=L) return query(l,mid,x<<1,L,R);
	return query(mid+1,r,x<<1^1,L,R);
}
int jump(int x,int k){
	for(int i=18;i>=0;i--) if(k>>i&1) x=up[x][i];
	return x;
}
void work(){
	int k=read();
	if(k>n) k=n;
	if(ans[k]){ printf("%d\n",ans[k]);return;}
	for(int i:vecd[k]) que.push(make_pair(depth[i],i));
	ans[k]=totleaf;cnt=0;
	while(!que.empty()){
		int u=que.top().second;que.pop();
        // cout<<"+++"<
		if(query(1,n,1,dfn[u],dfn[u]+siz[u]-1)<depth[u]+k) continue;
        // cout<<"---"<
		ans[k]++;used[++cnt]=u;
		modify(1,n,1,dfn[u],depth[u]);
		if(depth[u]>k){
			int nu=jump(u,k);
			que.push(make_pair(depth[nu],nu));
		}
	}
	printf("%d\n",ans[k]);
	for(int i=1;i<=cnt;i++) modify(1,n,1,dfn[used[i]],1e9);
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
	n=read();
	memset(h,-1,sizeof(h));
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y),add(y,x);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++) vecd[dis[i]].push_back(i);
	memset(seg,0x3f,sizeof(seg));
	for(int i=1;i<=n;i++) if(isleaf[i]) modify(1,n,1,dfn[i],depth[i]);
	for(int j=1;j<=18;j++) for(int i=1;i<=n;i++) up[i][j]=up[up[i][j-1]][j-1];
    // for(int i=1;i<=n;i++) cout<
	int q=read();
	while(q--) work();
	return 0;
}

你可能感兴趣的:(其他,算法)