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\) 位的系数。
所以只考虑 \(n\) 为偶数,奇数就先暴力转移一遍转化为偶数。
由此写出 \(f_{4,j},f_{8,j}\) 的转移柿子,寻找规律
其中 \(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\) 出发需要的最大资金,有
但是有环不能直接转移,因为答案不确定。
而一个点在没有出边的情况下答案确定。
考虑以某种顺序确定答案,
发现对于全局最大的 \(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;
}

浙公网安备 33010602011771号