11.13

没有模拟赛,做了几个题

P14460

\(dp\) 题,设 \(f_i\) 表示走到 \(i\) 的最短时间。
考虑一次性推完 \([j+1,i]\)
得到转移 \(f_i=Min(f_j+max(k*i-f_j-j*t2,0)+2*(i-j)*t1+j*t2)\)
复杂度是 \(n^2\)
考虑优化,打印出每个 \(i\) 的转移点,发现是递增的,所以可以维护上一个转移点,就做完了。

关于为什么是递增的,感性感觉不可能有 \(i,i+1\) 与转移点 \(j,k\) 满足 \(k<j\) ,因为此时肯定在 \([j,i]\) 中会有比 \(k\) 优的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int T,m,k,t1,t2,f[N];
void solve(){
	cin>>m>>k>>t1>>t2;
	int jc=1;
	for(int i=1;i<=m;i++){
		f[i]=k*i+t1*i;
		int now; 
		for(int j=jc;j<i;j++){
			now=f[j]+max(0ll,k*i-f[j]-j*t2)+2*j*t2+(i-j)*t1;
			if(f[i]>now){
				f[i]=now;
				jc=j;
			}
			else break;
		}
	}
	for(int i=1;i<=m;i++)cout<<f[i]<<" ";
	cout<<'\n';
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){solve();}
	return 0;
}

P14461

交替转移,实质是隔两个的转移,先写出柿子。
\(f_{i,j}\) 表示 \(i\) 阶,\(j\) 位的系数。

\[f_{i,j}=f_{i-2,j}+(j+1)(j+2)f_{i-2,j+2} \]

所以只考虑 \(n\) 为偶数,奇数就先暴力转移一遍转化为偶数。
由此写出 \(f_{4,j},f_{8,j}\) 的转移柿子,寻找规律

\[f_{8,j}=F_{j}-4*F_{j+2}*X_{j,2}+6*F_{j+4}*X_{j,4}-4*F_{j+6}*X_{j,6}+F_{j+8}*X_{j,8} \]

其中 \(X_{i,j}=i*(i+1)*...*(i+j)\)
那么就是一个组合数加上阶乘,\(m^2\) 转移。
而组合数永远是 \(C_{n/2}^{k}\) 可以只预处理底数为 \(n/2\) 的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5050,mod=1e9+7;
int n,m;
int f[N],g[N];
int ff[N],gg[N],cc[N];
int fac[N],inv[N];
int ksm(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans*=a;
		a*=a;
		ans%=mod,a%=mod;
		b>>=1;
	}
	return ans;
}
int ny(int x){
	return ksm(x,mod-2);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=0;i<=m;i++)cin>>f[i];
	for(int i=0;i<=m;i++)cin>>g[i];
	if(n&1){
		for(int i=0;i<=m;i++){
			ff[i]=g[i]+(i+1)*g[i+1]%mod;ff[i]%=mod;
			gg[i]=f[i]-(i+1)*f[i+1]%mod;gg[i]=(gg[i]+mod)%mod;
		}
		for(int i=0;i<=m;i++){
			f[i]=ff[i],g[i]=gg[i];
			ff[i]=gg[i]=0;
		}
		n=n/2*2;
	}
	cc[0]=1;
	for(int i=1;i<=min(n/2,m);i++){
		cc[i]=cc[i-1]*(n/2-i+1)%mod*ny(i)%mod;
	}
	fac[0]=1;
	for(int i=1;i<=m;i++)fac[i]=fac[i-1]*i%mod;
	inv[m]=ny(fac[m]);inv[0]=1;
	for(int i=m-1;i>=1;i--){
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	for(int i=0;i<=m;i++){
		for(int j=i;j<=m;j+=2){
			if(j-i>n)break;
			int o=((j-i)/2%2)?-1:1;
			int x=cc[(j-i)/2]*fac[j]%mod*inv[i]%mod;
			ff[i]=(ff[i]+x*o*f[j]%mod+mod)%mod;
		}
	}
	for(int i=0;i<=m;i++){
		for(int j=i;j<=m;j+=2){
			if(j>i+n)break;
			int o=((j-i)/2%2)?-1:1;
			int x=cc[(j-i)/2]*fac[j]%mod*inv[i]%mod;
			gg[i]=(gg[i]+x*o*g[j]%mod+mod)%mod;
		}
	}
	for(int i=0;i<=m;i++)cout<<ff[i]<<" ";
	cout<<"\n";
	for(int i=0;i<=m;i++)cout<<gg[i]<<" ";
	
	return 0;
}

P13078

一类关于最小生成树的思考方向,对于一条非树边 \([u,v]\) ,找到 \(lca\)
那么这条边会产生影响的边就是 \(u,v\) 路径上的边。
所以每次找到一条非树边就把路径上的全部归为一组,记录每组分到的最小编号\(mi\),然后记录当前已经预先支付的编号数量 \(ma\)
对于树边,如果已经被分组就查找最小编号并更新,否则直接分。
把边归到下面的节点考虑。
发现涉及处理树上链的处理,考虑使用并查集优化,指向上面第一个还没有被分的节点。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int n,m,head[N],idx;
int siz[N],de[N],to[N],_fa[N],son[N],id[N],idy;
struct edge{
	int v,next;
}e[N<<1];
struct ed{
	int u,v;
}edg[N];
void con(int u,int v){
	idx++;
	e[idx].v=v;
	e[idx].next=head[u];
	head[u]=idx;
}
void dfs(int u,int fa){
	siz[u]=1;
	_fa[u]=fa;
	de[u]=de[fa]+1;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs1(int u,int tx){
	to[u]=tx;
	id[u]=++idy;
	if(!son[u])return;
	dfs1(son[u],tx);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==_fa[u]||v==son[u])continue;
		dfs1(v,v);
	}
}
int mi[N],ma,ans,bel[N],cnt,is[N],bcj[N];
int lca(int u,int v){
	while(to[u]!=to[v]){
		if(de[u]>de[v])swap(u,v);
		v=_fa[to[v]];
	}
	if(de[u]>de[v])swap(u,v);
	return u;
}
int find(int x){
	if(bcj[x]==x)return x;
	return bcj[x]=find(bcj[x]);
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)bcj[i]=i;
	for(int i=1;i<=m;i++){
		cin>>edg[i].u>>edg[i].v;
	}
	for(int i=1;i<n;i++){
		int id;cin>>id;
		int u=edg[id].u,v=edg[id].v;
		is[id]=1;
		con(u,v);
		con(v,u);
	}
	dfs(1,0);
	dfs1(1,1);
	for(int i=1;i<=m;i++){
		int u=edg[i].u,v=edg[i].v;
		if(de[u]>de[v])swap(u,v);
		if(is[i]){
			if(bel[v]>0&&bel[v]<=m){
				ans=mi[bel[v]]+1;
				mi[bel[v]]++;
			}
			else{
				ans=++ma;
			}
			bel[v]=m+1;
		}
		else{
			cnt++;
			int lc=lca(u,v);
			int c=0;
			while(u!=v){
				if(de[u]>de[v]||v==lc)swap(u,v);
				if(!bel[v]){
					c++;
					bel[v]=cnt;
				}
				bcj[find(v)]=find(_fa[v]);
				if(de[bcj[v]]>=de[lc])v=bcj[v];
				else v=lc;
			}
			mi[cnt]=ma;
			ma+=c;
			ans=++ma;
		}
		cout<<ans<<" ";
	}
	
	return 0;
}

P7831

如果没有环,可以 \(dp\) ,设 \(f_i\) 表示从 \(i\) 出发需要的最大资金,有

\[f_u=Min(max(r_{u,v},f_v-w_{u,v})) \]

但是有环不能直接转移,因为答案不确定。
而一个点在没有出边的情况下答案确定。
考虑以某种顺序确定答案,
发现对于全局最大的 \(r\) 的那条边 \(u,v\) 那么 \(f_x\le r\) ,而此后对于其他点,这条边就没有贡献了,可以删去。
所以可以从 出度为 0 的点 开始拓展,在反图上拓朴,更新答案,删去边,同时按 \(r\) 从大到小遍历边,如果还没有删去,那么更新现在全局答案都会小于这个 \(r\)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,inf=1e9+10;
int n,m;
struct ed{
	int u,v,r,w,id;
}edg[N];
bool cm(ed x,ed y){
	return x.r>y.r;
}
int head[N],idx,ot[N],vis[N];
struct edge{
	int v,next,r,w,id;
}e[N];
void con(int u,int v,int r,int w,int id){
	idx++;
	e[idx].v=v;
	e[idx].next=head[u];
	e[idx].r=r;
	e[idx].w=w;
	e[idx].id=id;
	head[u]=idx;
}
queue<int>q;
int ans[N];
signed main(){
	cin>>n>>m;
	int ma=0;
	for(int i=1;i<=m;i++){
		int a,b,r,w;cin>>a>>b>>r>>w;
		edg[i]={a,b,r,w,i};
		ot[a]++;
		con(b,a,r,w,i);
	}
	sort(edg+1,edg+m+1,cm);
	for(int i=1;i<=n;i++){
		ans[i]=inf;
		if(ot[i]==0)q.push(i);
	}
	for(int i=1;i<=m;i++){
		while(!q.empty()){
			int u=q.front();
			q.pop();
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].v,id=e[i].id;
				if(vis[id])continue;
				vis[id]=1;ot[v]--;
				if(ans[u]<1e9)ans[v]=min(ans[v],max(e[i].r,ans[u]-e[i].w));
				if(ot[v]==0)q.push(v);
			}
		}
		int u=edg[i].u,id=edg[i].id;
		if(!vis[id]){
			ans[u]=min(ans[u],edg[i].r);
			vis[id]=1;
			ot[u]--;
			if(!ot[u])q.push(u);
		}
	}
	for(int i=1;i<=n;i++){
		if(ans[i]>1e9)ans[i]=-1;
		cout<<ans[i]<<" ";
	}
	
	return 0;
}
posted @ 2025-11-14 11:59  LC_Nocl  阅读(6)  评论(0)    收藏  举报