三月の题

目前还在刷AT

ARC075E

问有多少个区间的平均数大于等于 \(k\)。我们珂以把每个数变成它前面所有数的和与 \(k\times len\) 的差,这样子我们对数列排序后,如果一个元素在排序前后都在另一个元素之前,那么他们排序前之间的区间的平均数就大于等于 \(k\),正确性显然。珂以用树状数组维护。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define db long double
#define ll long long
#define mk make_pair
const ll N=3*114514,M=1919810,mod=92084931,inf=1e18;
ll n,k,sum[N],ans;
struct xx{
	ll id,val;
}a[N];
bool cmp(xx x,xx y){
	return x.val^y.val?x.val<y.val:x.id<y.id;
}
ll c[N];
ll lowbit(ll x){return x&-x;}
void update(ll x,ll k){
	while(x<=n){
		c[x]+=k,c[x]%=mod;
		x+=lowbit(x);
	}
}
ll query(ll x){
	ll ans=0;
	while(x){
		ans+=c[x],ans%=mod;
		x-=lowbit(x);
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;++i){
		ll x; cin>>x;
		sum[i]=sum[i-1]+x;
		a[i]=(xx){i,sum[i-1]-(i-1)*k};
	}
	a[++n]=(xx){n,sum[n-1]-(n-1)*k};
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;++i){
		ans+=(ans+query(a[i].id)%mod)%mod;
		update(a[i].id,1);
	}
	cout<<ans;
	return 0;
}

ARC074F

最小割,考虑 \(S\) 只连出边,\(T\) 只连入边,其它点连能够到达的点。对于限制每个点只会割一次有一个trick:拆点,拆成入点和出点,之间连一条流量为 \(1\) 的边,对于 到达它的点和它到达的点 都分别在入点和出点上连一条流量限制 \(\text{inf}\) 的点。但是这样边的数量是 \(n^3\) 级别的,考虑优化:将每行每列的点都连到一个对应那一行/列编号的点上,因为他们是都可以互相到达的,这样就时边数下降到 \(n^2\)。无解就是 \(S,T\) 在同一行/列,然后跑一遍最大流就行了,但是我反边建成 \(w\) 调了好久好久。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define db long double
#define ll long long
#define mk make_pair
const ll N=3*114514,M=1919810,mod=1e9+7,inf=1e18;
struct xx{
	ll next,to,val;
}e[2*N];
ll head[2*N],cnt=1;
void add(ll x,ll y,ll z){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	e[cnt].val=z;
	head[x]=cnt;
	e[++cnt].next=head[y];
	e[cnt].to=x;
	e[cnt].val=0; //?????????????草泥马 
	head[y]=cnt;
}
//我服了爸爸 
ll dept[30005],cur[2*N];
queue <ll> q;
ll h,w,n,s,t,sx,sy,tx,ty;
char c[105][105];
bool bfs(){
	memset(dept,0,sizeof(dept));
	q.push(s);
	dept[s]=1,cur[s]=head[s];
	while(!q.empty()){
		ll u=q.front(); q.pop();
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to,w=e[i].val;
			if(!dept[v]&&w){
				q.push(v);
				dept[v]=dept[u]+1;
				cur[v]=head[v];
			}
		}
	}
	return dept[t];
}
ll dfs(ll u,ll val){
	if(u==t) return val;
	ll use=0;
	for(int i=cur[u];i&&use<val;i=e[i].next){
		ll v=e[i].to,w=e[i].val; cur[u]=i;
		if(dept[v]==dept[u]+1&&w){
			ll res=dfs(v,min(w,val-use));
			if(!res) dept[v]=0;
			e[i].val-=res,e[i^1].val+=res;
			val-=res,use+=res;
		}
	}
	return use;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>h>>w; n=h*w;
	for(int i=1;i<=h;++i)
		for(int j=1;j<=w;++j){
			ll id=(i-1)*w+j;
			cin>>c[i][j];
			if(c[i][j]!='.'){
				if(c[i][j]!='S'&&c[i][j]!='T') add(id,id+n,1);
				if(c[i][j]=='S') s=id+n,sx=i,sy=j;
				if(c[i][j]=='T') t=id,tx=i,ty=j;
				add(id+n,2*n+i,inf);
				add(2*n+i,id,inf);
				add(id+n,2*n+h+j,inf);
				add(2*n+h+j,id,inf);
			}
		}
	if(sx==tx||sy==ty){
		cout<<-1;
		return 0;
	}
	ll ans=0,res;
	while(bfs())
		while((res=dfs(s,inf))) ans+=res;
	cout<<ans;
	return 0;
}

ABC065D

这个题乍一眼是完全图最小生成树,考虑 Boruvka 算法,但发现不会。考虑减少连边的数量,容易发现如果你要搞最小生成树,每个点只需要连在 \(x/y\) 轴上离它最近的点就行了,其他的都是多余的。
排两遍序,分别建边,kruskal,就做完了。

点击查看代码
//C++20求你别坑我了 
#include<bits/stdc++.h>
using namespace std;
#define db long double
#define ll long long
#define mk make_pair
const ll N=2*114514,M=1919810,mod=92084931,inf=1e18;
ll n,cnt;
struct edge{
	ll u,v,w;
}e[N];
struct xx{
	ll x,y,id;
}a[N];
bool cmp1(xx x,xx y){
	return x.x^y.x?x.x<y.x:x.y<y.y;
}
bool cmp2(xx x,xx y){
	return x.y^y.y?x.y<y.y:x.x<y.x;
}
bool cmp3(edge x,edge y){
	return x.w<y.w;
}
ll f[N];
ll find(ll x){return x==f[x]?x:f[x]=find(f[x]);}
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].x>>a[i].y,a[i].id=i,f[i]=i;
	sort(a+1,a+n+1,cmp1);
	for(int i=1;i<n;++i) e[++cnt]=(edge){a[i].id,a[i+1].id,a[i+1].x-a[i].x};
	sort(a+1,a+n+1,cmp2);
	for(int i=1;i<n;++i) e[++cnt]=(edge){a[i].id,a[i+1].id,a[i+1].y-a[i].y};
	ll ans=0,tot=0;
	sort(e+1,e+cnt+1,cmp3);
	//for(int i=1;i<=cnt;++i) cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<'\n';
	for(int i=1;i<=cnt;++i){
		ll u=e[i].u,v=e[i].v,w=e[i].w;
		ll x=find(u),y=find(v);
		if(x!=y){
			f[y]=x;
			ans+=w,++tot;
			if(tot==n-1) break;
		}
	}
	cout<<ans;
	return 0;
}

ARC081E

wht给我讲的。我们倒着遍历串,每扫齐一组 \(a\)\(z\) 就记录下这一组的开头位置。然后从头开始扫,每次找当前组中第一个没有出现的字符放进答案字符串里,然后在下一组中找到这个字符,再继续递归下去,正确性怎么证的忘了,感性理解。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define db long double
#define ll long long
#define mk make_pair
const ll N=2*114514,M=1919810,mod=92084931,inf=1e18;
char s[N],ans[N];
ll n,m,pos[N],cnt;
bool f[N];
void calc(ll id,ll cnt){
	memset(f,0,sizeof(f));
	for(int i=id;i<pos[cnt];++i) f[s[i]-'a']=1;
	ll ch;
	for(int i=0;i<26;++i)
		if(!f[i]){
			ch=i;
			break;
		}
	ans[++m]=char(ch+'a');
	for(int i=pos[cnt];i<=n;++i)
		if(s[i]-'a'==ch){
			calc(i+1,cnt-1);
			break;
		}
}//难想 
int main(){
	//ios::sync_with_stdio(0);
	//cin.tie(0); cout.tie(0);
	scanf("%s",s+1); n=strlen(s+1);
	ll tot=0;
	for(int i=n;i>=1;--i){
		if(!f[s[i]-'a']){
			++tot;
			if(tot==26){
				tot=0;
				memset(f,0,sizeof(f));
				pos[++cnt]=i;
			}
			else f[s[i]-'a']=1;
		}
	}
	pos[0]=n;
	calc(1,cnt);
	printf("%s",ans+1);
	return 0;
}

ARC076E

你会发现一对点如果至少一个不在边界上,那么它的连线是可以随便绕的,所以只考虑边界上的点对。我们珂以对每个点标一个统一的距离,这里可以设为每个点顺时针到左上角 \((1,1)\) 的距离。距离求出来之后排序,然后这就成一个括号匹配问题了,用个栈维护即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2*114514,M=1919810;
ll r,c,n,cnt;
ll st[N],top;
struct xx{
	ll x,y,id,val;
}a[N];
bool cmp(xx x,xx y){
	return x.val<y.val;
}
ll calc(ll x,ll y){
	//左下右上 
	if(y==0) return x;
	else if(x==r) return r+y;
	else if(y==c) return r+c+(r-x);
	else return r+c+r+(c-y);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>r>>c>>n;
	for(int i=1;i<=n;++i){
		ll x,y,u,v;
		cin>>x>>y>>u>>v;
		if((x>0&&x<r&&y>0&&y<c)||(u>0&&u<r&&v>0&&v<c)) continue;
		a[++cnt]=(xx){x,y,i,calc(x,y)};
		a[++cnt]=(xx){u,v,i,calc(u,v)};
	}
	sort(a+1,a+cnt+1,cmp);
	for(int i=1;i<=cnt;++i)
		if(st[top]==a[i].id) --top;
		else st[++top]=a[i].id;
	if(top) cout<<"NO";
	else cout<<"YES";
	return 0;
}//沙包AT求您消停下吧

CF1251E1&CF1251E2&P6346

贪心。考虑先买 \(m\) 尽量大的人,这样可以保证如果有 \(m\) 较小的就一定能先白嫖到。用一个优先队列维护每个人的 \(p\),如果当前满足不了这个人就在优先队列里取一个 \(p\) 最小的人买了,因为有数据限制所以接下来肯定能获得这个人,最后用总钱数减去省下的钱。
看下代码更直观。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2*114514,M=1919810;
ll T;
ll n,sum,ans,val;
struct xx{
	ll m,p;
}a[N];
bool cmp(xx x,xx y){
	return x.m>y.m;
}
priority_queue<ll,vector<ll>,greater<ll> > q;
void solve(){
	cin>>n; sum=ans=val=0;
	while(!q.empty()) q.pop();
	for(int i=1;i<=n;++i) cin>>a[i].m>>a[i].p,sum+=a[i].p;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;++i){
		val+=a[i].p; q.push(a[i].p);
		if(a[i].m>n-q.size()){
			val-=q.top();
			q.pop();
		}
		ans=max(ans,val);
	}
	cout<<sum-ans<<'\n';
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>T;
	while(T--) solve();
	return 0;
}

ARC091E

已经写过题解了

CF1217D

给一个有向图的所有边着色,使得没有一个环只有一个颜色,需要最小化使用颜色的数量。

先拓扑排序判一下有没有环,没有就全输出 \(1\)。Aria_Math 给了一种特别牛的方法:记录下每条边 \((a,b)\)\(a\le b\) 输出 \(1\),反之 \(2\)
考虑反证法:如果如果只有一种颜色则所有边都是从小到大或者所有边都是从大到小,根本不能构成环。做完了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=5005,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,m,in[N],tot;
queue <ll> q;
void bfs(){
	for(int i=1;i<=n;++i)
		if(!in[i]) q.push(i);
	while(!q.empty()){
		ll u=q.front(); q.pop();
		++tot;
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to;
			--in[v];
			if(!in[v]) q.push(v);
		}
	}
}//忘了topo怎么写了,我紫菜
ll a[N],b[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>a[i]>>b[i];
		add(a[i],b[i]),++in[b[i]];
	}
	bfs();
	if(tot==n){
		cout<<"1\n";
		for(int i=1;i<=m;++i) cout<<"1 ";
	}
	else{
		cout<<"2\n";
		for(int i=1;i<=m;++i)
			if(a[i]<b[i]) cout<<"2 ";
			else cout<<"1 ";
	}
	return 0;
}

P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III

区间众数出现次数,强制在线。

我们珂以预处理块之间的众数出现次数前缀和,反正我们不管众数具体是哪个,可以 \(O(n\sqrt{n})\) 实现,这样解决整块查询。
对于散块我一开始的想法是记录块之间每个数的出现次数前缀和,然后分别暴力扫两边散块,加上中间整块处理的次数前缀和。正确性毋庸置疑,但这题空间只给你开了 \(\text{62.50MB}\),所以这个前缀和数组就直接 MLE 了。
更好的做法:对于每个数记录它所有的出现位置,可以用vector,并记录 \(id_i\) 表示这个位置在对应数的 vector 中是第几个。\(ans\) 初始赋为中间整块的答案,然后暴力扫两边散块,对于左边散块,如果 id[i]+ans<g[a[i]].size()&&g[a[i]][id[i]+ans]<=r,表示 \(a_i\) 的总出现次数大于等于这段区间内已知的众数出现次数 并且 \(a_i\) 的第 \(id_i+ans\) 个出现位置小于等于 \(r\),那么就接着 \(++ans\) 继续看 \(a_i\) 是否满足。右边散块反过来就行了。具体看看代码。
不过竟然没怎么卡时间。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=500005,M=1919810,B=777;
inline ll read(){
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return x*f;
}
inline void write(ll x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
    return;
}
ll MAX(ll a,ll b){return a>=b?a:b;}
ll MIN(ll a,ll b){return a<=b?a:b;}
ll n,m,las,a[N],b[N];
ll ns,nq,bel[N],st[B],ed[B];
ll sum[B][B],cnt[N],id[N];
vector <ll> g[N];
//ll num[B][N]; //块之间次数前缀和
//神特么vector记录位置 
void query(ll l,ll r){
	l^=las,r^=las;
	if(l>r) swap(l,r);
	ll ans=0;
	if(bel[l]==bel[r]){
		for(int i=l;i<=r;++i) cnt[a[i]]=0;
		for(int i=l;i<=r;++i) ans=MAX(ans,++cnt[a[i]]);
		las=ans;
		write(ans),putchar('\n'); return;
	} 
	ans=sum[bel[l]+1][bel[r]-1]; //整
	for(int i=l;i<=ed[bel[l]];++i)
		while(id[i]+ans<(ll)g[a[i]].size()&&g[a[i]][id[i]+ans]<=r) ++ans;
	for(int i=st[bel[r]];i<=r;++i)
		while(id[i]-ans>=0&&g[a[i]][id[i]-ans]>=l) ++ans;
	las=ans;
	write(ans),putchar('\n'); return;
}
int main(){
	n=read(),m=read(); ns=sqrt(n),nq=ceil(n*1.0/ns);
	for(int i=1;i<=n;++i) a[i]=read(),b[i]=a[i];
	sort(b+1,b+n+1);
	ll nm=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;++i){
		a[i]=lower_bound(b+1,b+nm+1,a[i])-b;
		g[a[i]].push_back(i);
		id[i]=g[a[i]].size()-1;
	}
	for(int i=1;i<=nq;++i){
		st[i]=ns*(i-1)+1,ed[i]=ns*i;
		for(int j=st[i];j<=ed[i];++j) bel[j]=i,++cnt[a[j]];
	}ed[nq]=n;
	for(int i=1;i<=nq;++i){
		memset(cnt,0,sizeof(cnt));
		for(int j=i;j<=nq;++j){
			sum[i][j]=sum[i][j-1];
			for(int k=st[j];k<=ed[j];++k)
				sum[i][j]=MAX(sum[i][j],++cnt[a[k]]);
		}
	}
	while(m--) query(read(),read());
	return 0;
}/*10 1
1 1 4 5 1 4 1 9 4 9
我服了88*/

CF990G GCD Counting

一颗 \(n\) 个点的树,每个点有点权 \(a_i\),定义 \(g(x,y)\)\(x\)\(y\) 的路径上所有点权的 \(\text{gcd}\)。对于每个正整数 \(k\in[1,2\times 10^5]\),求满足 \(g(x,y)=k,1\le x\le y\le n\)\((x,y)\) 对数。没有则不输出。

点分治。set 记录点权,最大公约数最多有一百多个不同的。
找重心错了,很奇怪,似乎是子树大小的锅还是啥不知道。但我想睡觉

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define lxl long long
const ll N=2*114514,M=1919810,B=777;
ll gcd(ll a,ll b){return a?gcd(b%a,a):b;} //总不能是你吧 
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],maxa;
ll rt,siz[N],maxp[N],sum;
ll msiz;
bool vis[N];
void findrt(ll u,ll fa){
	siz[u]=1;
	ll maxn=0;
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa||vis[v]) continue;
		findrt(v,u);
		siz[u]+=siz[v];
		maxn=max(maxn,siz[v]);
	}
	maxn=max(maxn,sum-siz[u]);
	if(maxn<msiz) msiz=maxn,rt=u; //88我服了
	//我去凭什么啊,真的服了 
}
lxl ans[N],cont[N],tot[N];
set <ll> s,se;
void dfs_val(ll u,ll fa,ll val){
	if(!cont[val]++) s.insert(val); //???
	++ans[val];
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs_val(v,u,gcd(val,a[v]));
	}
}
void calc(ll u){
	++ans[a[u]];
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(vis[v]) continue;
		dfs_val(v,u,gcd(a[u],a[v]));
		for(auto y:se)
			for(auto x:s)
				ans[gcd(x,y)]+=cont[x]*tot[y];
		for(auto x:s) se.insert(x),tot[x]+=cont[x],cont[x]=0;
		s.clear(); //我服了爸爸我服了爸爸我服了爸爸
		//这下是真的服了 
	}
	for(auto x:se) tot[x]=0;
	se.clear();
}
void solve(ll u){
	calc(u);
	vis[u]=1;
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(vis[v]) continue;
		msiz=sum=siz[v];
		findrt(v,u),solve(rt);
	}
}
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],maxa=max(maxa,a[i]);
	for(int i=1;i<n;++i){
		ll x,y; cin>>x>>y;
		add(x,y),add(y,x);
	}
	sum=msiz=n;
	findrt(1,0);
	solve(rt);
	for(int i=1;i<=maxa;++i)
		if(ans[i]) cout<<i<<" "<<ans[i]<<'\n';
	return 0;
}//我服了爸爸 

ARC122C

发现先加个一然后不断交替搞 \(3/4\) 操作得出来的是个斐波那契数列,那么可以考虑怎么用斐波那契来逼近 \(n\)\(fib(8)>1e18\),有一个定理:任意数都能被分解为若干个斐波那契数之和,并且这些数不会再合成新的斐波那契数。考虑在加斐波那契的过程中进行 1/2 操作,这次操作的贡献是会分奇偶的,就可以考虑在增加数列的某一位判断奇偶来加 \(1/2\)
每次星期天来了都想睡觉,头晕。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mk make_pair
const ll N=2*114514,M=1919810,inf=1145141919810;
ll n,s,f[N],vis[N];
//斐波那契逼近
vector <ll> g;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	f[0]=0,f[1]=1;
	for(int i=2;i<=88;++i) f[i]=f[i-1]+f[i-2];
	for(int i=87;i>=1;--i){
		if(n>=f[i]){
			n-=f[i];
			vis[i]=1;
			if(!s) s=i;
		}
	}
	if(s&1) g.push_back(1);
	else g.push_back(2);
	for(int i=s-1;i>=1;--i){
		if(i&1) g.push_back(3);
		else g.push_back(4);
		if(vis[i]){
			if(i&1) g.push_back(1);
			else g.push_back(2);
		}
	}
	cout<<g.size()<<'\n';
	for(int i:g) cout<<i<<'\n';
	return 0;
}

ARC112D

这个题要简单一些。很容易能发现每一行每一列都需要至少有一个能到达的 #,并且你想到达一个 # 只需要变一个格子,如果一堆 # 可以互相到达那么也只需要变一个。那么可以考虑用并查集把能互相到达的行和列连接起来,最后分别考虑是把每行变一个还是每列变一个更优,有时候两个贪心取最小还确实是对的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mk make_pair
const ll N=2*1145,M=1919810,inf=1145141919810;
ll n,m; //好好好 
char c[N][N];
bool f[N][N],col[2*N];
//每一行/列必须有个能到达的#
ll fa[2*N];
ll find(ll x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(ll a,ll b){
	ll x=find(a),y=find(b);
	if(x==y) return;
	fa[y]=x;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n+m;++i) fa[i]=i;
	merge(1,n),merge(1,n+1),merge(1,n+m);
	merge(n,n+1),merge(n,n+m);
	merge(n+1,n+m);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			cin>>c[i][j];
			if(c[i][j]=='#') merge(i,n+j);
		}
	ll ans1=0,ans2=0;
	for(int i=1;i<=n;++i)
		if(!col[find(i)]) ++ans1,col[find(i)]=1;
	--ans1;
	for(int i=1;i<=n+m;++i) col[i]=0;
	for(int j=1;j<=m;++j)
		if(!col[find(n+j)]) ++ans2,col[find(n+j)]=1;
	--ans2;
	cout<<min(ans1,ans2);
	return 0;
}

睡觉去了。

ARC122D

01trie

CF1004E

关键结论:选的点一定都尽量在直径上,一定更优。感性理解一下,因为树中的其他路径都比直径短,所以如果把点换到别的路径上答案一定会增加。
那么我们就在直径上来选连续的 \(k\) 个点,这个直接单调枚举即可,然后再统计不在直径上的点到路径的距离,取 \(\text{max}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define mk make_pair
const ll N=114514,M=1919810,inf=2e9;
struct xx{
	ll next,to,val;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y,ll z){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	e[cnt].val=z;
	head[x]=cnt;
}
ll n,k;
ll ans=inf;
bool vis[N];
ll st,ed,dis[N],f[N];
void dfs(ll u,ll fa){
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to,w=e[i].val;
		if(v==fa||vis[v]) continue;
		dis[v]=dis[u]+w;
		f[v]=u;
		dfs(v,u);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<n;++i){
		ll a,b,c;
		cin>>a>>b>>c;
		add(a,b,c),add(b,a,c);
	}
	dfs(1,0);
	ll maxn=0;
	for(int i=1;i<=n;++i)
		if(dis[i]>maxn) maxn=dis[i],st=i;
	memset(dis,0,sizeof(dis));
	f[st]=0;
	dfs(st,0);
	maxn=0;
	for(int i=1;i<=n;++i)
		if(dis[i]>maxn) maxn=dis[i],ed=i;
	for(int i=ed;i;i=f[i]) vis[i]=1;
	ll l=ed,r=ed;
	for(int i=1;i<k&&f[r];++i) r=f[r];
	while(l&&r){
		ans=min(ans,max(dis[ed]-dis[l],dis[r]));
		l=f[l],r=f[r];
	}
	for(int i=ed;i;i=f[i]) dis[i]=0,dfs(i,0);
	for(int i=1;i<=n;++i)
		if(!vis[i]) ans=max(ans,dis[i]);
	if(ans!=inf) cout<<ans;
	else cout<<0;
	return 0;
}/*8 4
8 7 4
5 6 7
7 3 4
8 4 3
1 2 1
2 3 5
5 4 4
被 1 1hack了,淦 傻呗
我服了爸爸*/

CF1032F

树上计数,考虑 dp。如果要让一个子树的最大匹配唯一,那么这个子树要么只有一个点要么就是偶数个点并且能两两配对。那么考虑将是否匹配放进状态里,

posted @ 2024-03-06 14:41  和蜀玩  阅读(29)  评论(0)    收藏  举报