2019 6.30
模拟赛 没有AK 那么总结还是得写的。题目还是很不错的。就是我很菜。
题目比较好懂,数据范围n,m<=5000. 很容易想到树形dp 当然我想的是 需要换一下根两次dp 即可。
有同学 是直接发现这是一个点权重心的问题 我们可以求出其点权的重心,然后直接算答案 复杂的当然 也是nm的。
当然还有方法是一开始把所有点当做补给站的答案都统计出来然后修改的时候把受到影响的路径统计一番。复杂的也是nm.的
当n达到了100000呢好像不太友好了 那么 思路就应该从点分治出发了,但是每次都是修改那么显然是动态点分治问题->点分树问题。(当然我学了几天学的很懵逼自己还不能把这个代码写出来)
所以 先留坑。放考试代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000000 #define ll long long #define min(x,y) (x>y?y:x) #define max(x,y) (x>y?x:y) #define RI register ll #define up(p,i,n) for(ll i=p;i<=n;++i) #define db double using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=10010; //树形dp 复杂度 nm 考虑换根操作 //注意 开 long long int n,m,len,minn,sum; int g[MAXN],sz[MAXN],f[MAXN],d[MAXN]; int lin[MAXN],nex[MAXN<<1],ver[MAXN<<1],e[MAXN<<1]; inline void add(int x,int y,int z) { ver[++len]=y;nex[len]=lin[x];lin[x]=len;e[len]=z; ver[++len]=x;nex[len]=lin[y];lin[y]=len;e[len]=z; } inline void dfs(int x,int father) { sz[x]=g[x];d[x]=0; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(tn==father)continue; dfs(tn,x); sz[x]+=sz[tn]; d[x]+=d[tn]+sz[tn]*e[i]; } return; } inline void dp(int x,int father) { minn=min(minn,f[x]); for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(tn==father)continue; f[tn]=d[tn]+(sum-sz[tn])*e[i]+(f[x]-d[tn]-sz[tn]*e[i]); dp(tn,x); } } int main() { //freopen("1.in","r",stdin); //freopen("game.in","r",stdin); //freopen("game.out","w",stdout); n=read();m=read(); for(int i=1;i<n;++i) { int x,y,z; x=read();y=read();z=read(); add(x,y,z); } for(int i=1;i<=m;++i) { int x,y; x=read();y=read(); g[x]+=y;minn=INF; dfs(1,0);//printf("%d\n",d[1]); f[1]=d[1];sum=sz[1]; dp(1,0); printf("%lld\n",minn); } return 0; }
这道题很早以前写过,也多亏了当时听了一波线段树合并给听懂了。
其实是裸的树上差分 然后最后合并一下求最大值就行了,复杂度很低当然也是一个近乎nlogn的复杂度。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//#include<bits/stdc++.h> #include<iostream> #include<cstdio> #include<iomanip> #include<cstring> #include<string> #include<cstdlib> #include<cmath> #include<algorithm> #include<cctype> #include<utility> #include<set> #include<bitset> #include<queue> #include<stack> #include<deque> #include<map> #include<vector> #include<ctime> #define ll long long #define INF 2147483646 #define R register #define r(i) t[i].r #define l(i) t[i].l #define sum(i) t[i].sum #define v(i) t[i].v using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } inline void put(int x) { x<0?putchar('-'),x=-x:0; int num=0;char ch[50]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar('\n');return; } const int MAXN=100002; int n,m,cnt,num; int lin[MAXN<<1],ver[MAXN<<1],nex[MAXN<<1],len; int a[MAXN],b[MAXN],f[MAXN],fa[MAXN],lca[MAXN],s[MAXN],s1[MAXN],vis[MAXN],root[MAXN],ans[MAXN]; vector<int>q[MAXN],q1[MAXN]; struct wy { int l,r; int sum;//次数 int v;//价值 }t[MAXN<<6]; inline void add(int x,int y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; } inline void discrete() { sort(b+1,b+1+m); for(int i=1;i<=m;++i)if(i==1||b[i]!=b[cnt])b[++cnt]=b[i]; for(int i=1;i<=m;++i)a[i]=lower_bound(b+1,b+1+cnt,a[i])-b; } inline int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);} inline int max(int x,int y){return x>y?x:y;} inline int min(int x,int y){return x>y?y:x;} inline void tarjan(int x) { vis[x]=1; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(vis[tn]==1)continue; tarjan(tn); f[tn]=x;fa[tn]=x; vis[tn]=2; } for(unsigned int i=0;i<q[x].size();++i) { int tn=q[x][i]; if(vis[tn]==2)lca[q1[x][i]]=getfather(tn); } return; } inline void change(int &p,int l,int r,int x,int d) { if(p==0)p=++num; if(l==r){sum(p)+=d,v(p)=l;return;} int mid=(l+r)>>1; if(x<=mid)change(l(p),l,mid,x,d); else change(r(p),mid+1,r,x,d); sum(p)=max(sum(l(p)),sum(r(p))); v(p)=sum(l(p))>=sum(r(p))?v(l(p)):v(r(p)); return; } inline int merge(int x,int y,int l,int r) { if(x==0||y==0)return x+y; if(l==r){sum(x)+=sum(y);return x;} int mid=(l+r)>>1; l(x)=merge(l(x),l(y),l,mid); r(x)=merge(r(x),r(y),mid+1,r); sum(x)=max(sum(l(x)),sum(r(x))); v(x)=sum(l(x))>=sum(r(x))?v(l(x)):v(r(x)); return x; } inline void dfs(int x) { vis[x]=1; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(vis[tn]==1)continue; dfs(tn); root[x]=merge(root[x],root[tn],1,cnt); } if(sum(root[x])==0)ans[x]=0; else ans[x]=v(root[x]); return; } int main() { //freopen("1.in","r",stdin); //freopen("rain.in","r",stdin); //freopen("rain.out","w",stdout); n=read();m=read(); for(int i=1;i<n;++i) { int x,y;f[i]=i; x=read();y=read(); add(x,y);add(y,x); } f[n]=n; for(int i=1;i<=m;++i) { s[i]=read();s1[i]=read(); a[i]=b[i]=read(); if(s[i]==s1[i]){lca[i]=s[i];continue;} q[s[i]].push_back(s1[i]); q1[s[i]].push_back(i); q[s1[i]].push_back(s[i]); q1[s1[i]].push_back(i); } discrete(); tarjan(1); for(int i=1;i<=m;++i) { change(root[s[i]],1,cnt,a[i],1); change(root[s1[i]],1,cnt,a[i],1); change(root[lca[i]],1,cnt,a[i],-1); if(fa[lca[i]])change(root[fa[lca[i]]],1,cnt,a[i],-1); } memset(vis,0,sizeof(vis)); dfs(1); for(int i=1;i<=n;++i)put(b[ans[i]]); return 0; }
虽然第一次学习哈夫曼树 但是 我对它的理解可以说是非常的深,我明白它在干什么。。
当然 这都是源于我的假贪心 。 我原本的思路是这样的 显然 si越小越好,那么 还要满足si不是sj的前缀。显然假设我si先取最小的我可能面临着字符串不够用的情况。
但是 我有一种很巧妙的发现实 每一个si都可以再多生成k个节点 然后虽然长度+1但是字符串更多了 把si扔掉那么现在所有的字符串都是合法的,然后 给ai排个序求一波答案即可。
当然这个贪心并不是最优的所以是错误的贪心 以为我不知道哪个节点延伸 哪个节点不延伸 可以一个节点延伸多次比另一个节点不延伸 更优 因为这样可以保证哪个最大的ai能匹配上最小的si
这我都是不知道的无法控制 自然这个贪心就显得 不太行了。
听鑫神讲了波课 才发现我这个贪心正是哈夫曼树的逆过程。。。没想到倒着搞就是答案。。太菜了。。
试想一下这几个节点我不知道深度是多少 但是我可以利用合并来合并出最优的结果。这个过程和上述过程完全相反 但是却可以得到最优的答案 因为堆的贪心。
至于第二问则是求出si 最小也就是哪个字符串最短 也就是向下延伸的最少自然对于正过程就是字符串合并整体层数越小越好那么在答案最优的情况下层数小的优先即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000000 #define ll long long #define min(x,y) (x>y?y:x) #define max(x,y) (x>y?x:y) #define RI register ll #define up(p,i,n) for(ll i=p;i<=n;++i) #define db double using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } //或许是因为梦的缘故 琉璃之人追逐幻影 ---龙族 ll n,k; struct wy { ll sum,depth; ll friend operator <(wy a,wy b) { return a.sum==b.sum?a.depth>b.depth:a.sum>b.sum; } }; ll sum,ans; priority_queue<wy> q; signed main() { //freopen("1.in","r",stdin); n=read();k=read(); for(ll i=1;i<=n;++i)q.push((wy){read(),0}); if((n-1)%(k-1)!=0) { ll w=k-1-((n-1)%(k-1)); for(ll i=1;i<=w;++i)q.push((wy){0,0}); } while(q.size()!=1) { ll cnt=0,w=0; for(ll i=1;i<=k;++i) { w=max(w,q.top().depth); cnt+=q.top().sum; q.pop(); } sum+=cnt; q.push((wy){cnt,w+1}); } printf("%lld\n",sum); printf("%lld",q.top().depth); return 0; }