七月の题

ARC160D

直接考虑从 \(A\) 变为全零数列不太好做,考虑将问题转化为从全零数列通过两种操作可以得到的 \(A\) 数列的个数。发现只要满足每个区间的加一次数 \(\le k\) 就能保证不同操作序列得到数列的唯一性,这个很好感性理解。

于是题目转化成统计序列 \(b_{1\sim 2n-k+1}\) 的个数,要求:

  • \(\sum\limits_{i=1}^{2n-k+1}b_i=\dfrac{m}{k}\)

  • \(\forall i\in[1,n-k+1],b_i<k\)

这是一个有上界的插板法,容斥,钦定 \(i\) 个元素不合法,其余任意,那我们得到:$$ans=\sum\limits_{i=0}^{n-k+1} (-1)^i\dbinom{n-k+1}{i}\dbinom{\frac{m}{k}-ik+2n-k}{2n-k}$$

注意 \(m\) 很大所以无法用数组存组合数,直接暴力计算即可,时间复杂度 \(O(n^2\log mod)\)

P7880 [Ynoi2006] rldcot

树上点对计数。对于每个点 \(x\) 考虑其作为 \(\text{lca}\) 贡献:当区间中包含 \(x\) 或是有一对点来自 \(x\) 的不同子树,设为 \((a,b)\),钦定 \(a<b\),但这样的点最多是 \(O(n^2)\) 的。可以发现对于点对 \((a,b),(c,d)\),若 \(a\le c,d\le b\),那么 \((a,b)\) 就是无用的,因为包含了 \((a,b)\) 就一定包含了 \((c,d)\),也就是找区间内的支配对就行。通过 dus on tree 求出所有支配对共 \(O(n\log n)\) 对,再进行查询。


博客园就是好,直接把图片拖进编辑器里就好了,等我上大学了一定要支持。

P9755 [CSP-S 2023] 种树

过了将近一年再来做,思路确实是深入很多了,只差一些步骤就独立做出来了,不过还是说明我太菜了QAQ

正着直接去找最小值有些困难,可以自然想到二分答案,那么现在的问题是如何找到一个点的树所需生长的时间。我们假设种下每棵树的时间已经确定,为 \(l\),目前二分到的答案为 \(r\),考虑把函数中的 \(\max\) 去掉,也就是分讨一下。

这样我们就解决了对时间的判定问题,然后考虑种树的顺序怎样是最优的。可以先考虑 A 性质怎么做:我们直接将节点按所需生长的时间 \(t\) 排序,按着这个顺序来种树肯定是最优的,所以就可以直接顺序遍历,如果当前点未种树,就把它以及它的祖先中没种树的给种上并标记。最后令 \(s_i\)\(i\) 点的种树时间,若 \(s_i+t_i-1\) 的最大值大于二分的答案,那么 return 0;

然后考虑正解,其实和 A 性质差不多。因为种树时间会影响树的生长时间,所以就不能沿用 A 的做法。考虑将 \(t_i\) 的定义改为 \(i\) 点合法所需的最晚种树时间,这个可以对每个点二分得到。将 \(t\) 从小到大排序,问题就又转化为了 A 性质的问题,直接同样做法解决就行。

时间复杂度 \(O(n\log n\log V)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lxl __int128
const ll N=114514,M=1919810;
struct xx{
	ll next,to;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
ll n,a[N],b[N],c[N];
ll id[N],low[N],s[N],f[N];
bool cmp(ll x,ll y){return low[x]<low[y];}
lxl calc(ll id,lxl l,lxl r){
	lxl len=r-l+1;
	if(c[id]>=0) return len*b[id]+len*(l+r)/2*c[id];
	lxl tim=(1-b[id])/c[id];
	if(tim<l) return len;
	if(tim>r) return len*b[id]+len*(l+r)/2*c[id];
	return (tim-l+1)*b[id]+(tim-l+1)*(l+tim)/2*c[id]+r-tim;
	//囸 你 骂 
}
bool vis[N];
bool check(ll r){
	for(int i=1;i<=n;++i){
		if(calc(i,1,r)<a[i]) return 0;
		ll lx=1,rx=n,res=0;
		while(lx<=rx){
			ll mid=(lx+rx+1)>>1;
			if(calc(i,mid,r)>=a[i]) lx=mid+1,res=mid;
			else rx=mid-1;
		}
		low[i]=res,id[i]=i,vis[i]=0;
	}
	sort(id+1,id+n+1,cmp);
	for(int i=1,j=0;i<=n;++i){
		ll u=id[i],top=0;
		while(!vis[u]) s[++top]=u,vis[u]=1,u=f[u];
		while(top) if(low[s[top--]]<++j) return 0;
	}
	return 1;
}
void dfs1(ll u,ll fa){
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa) continue;
		f[v]=u,dfs1(v,u);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i]>>b[i]>>c[i];
	for(int i=1;i<n;++i){
		ll a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
	}
	dfs1(1,0),vis[0]=1;
	ll l=n,r=1e9,ans;
	while(l<=r){
		ll mid=(l+r)>>1;
		if(check(mid)) r=mid-1,ans=mid;
		else l=mid+1;
	}
	cout<<ans;
	return 0;
}

P5157 [USACO18DEC] The Cow Gathering P

手玩一下发现,我们每次删点都是删叶子,如果删非叶节点的话就不满足题设。尝试去发掘一些性质,可以发现两个结论:

1.所有合法节点形成一个连通块。

2.所有合法节点中不包含 \(m\) 个限制中的 \(a_i\)

手玩得到不太会证。有了这两个性质做法就显然了,我们先通过某种方法求出一个合法点,再一遍 dfs 就可以了。对于求一个合法点,可以考虑去模拟删点的过程,由于每次要删的是叶节点,也就是度数为 \(1\) 的点,那么可以用拓扑排序的方式去维护,如果入队的点数 \(\le n\) 就无解。对于限制也可以把它看作是一条 \(a\) 连向 \(b\) 的有向边并且对 \(b\) 算上这条入度,这样就可以保证要 \(a\) 先入队 \(b\) 才能进队。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=1919810;
struct xx{
	ll next,to;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
vector <ll> g[N];
ll n,m,a[N],b[N],du[N],tot;
bool ans[N];
void dfs(ll u,ll fa){
	if(g[u].size()) return;
	ans[u]=1;
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<n;++i){
		ll a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
		++du[a],++du[b];
	}
	for(int i=1;i<=m;++i){
		cin>>a[i]>>b[i];
		g[a[i]].push_back(b[i]); ++du[b[i]];
	}
	queue <ll> q;
	for(int i=1;i<=n;++i)
		if(du[i]==1) q.push(i);
	ll u=0;
	while(!q.empty()){
		++tot;
		u=q.front(); q.pop();
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to;
			if(--du[v]==1) q.push(v);
		}
		for(int v:g[u])
			if(--du[v]==1) q.push(v);
	}
	if(tot<n){
		for(int i=1;i<=n;++i) cout<<"0\n";
		return 0;
	}
	dfs(u,0);
	for(int i=1;i<=n;++i) cout<<ans[i]<<'\n';
	return 0;
}
posted @ 2024-07-17 17:49  和蜀玩  阅读(19)  评论(0)    收藏  举报