noip 2018

积木大赛

思路:

当前块的积木(设位置为i)对答案的贡献只与上一块积木高度(h[i-1])有关。
若上一块的最终高度大于当前块,则在搭建上一块积木时可通过改变区间端点的方式顺带搭建好当前块。
若上一块积木高度小于当前块,则它最多只能顺带为当前块搭建 h[i-1] 的高度,剩下的高度要另外搭建,则当前块对答案贡献为 h[i]-h[i-1] 。

代码
#include<bits/stdc++.h>
using namespace std;const int maxn=1e5+5;
int n,la,ans;
int h[maxn];int main(){scanf("%d",&n);for(int i=1;i<=n;++i){scanf("%d",&h[i]);if(h[i]>la){ans+=h[i]-la;}la=h[i];}printf("%d",ans);return 0;
}

货币系统

心得:

虽然以前A了,但今天还是忘了,又只有看题解。其实这不是忘不忘的问题,主要是自己对做过的题没有熟练的掌握。而且,遇到看上去没有头绪的题不会大胆猜想结论,思维能力还有待提高。话说那些大佬一眼就看出来的“显然”结论我怎么看不出来显然?也许是自己懒得去想吧……

思路:

(蒟蒻的证明讲的可能不是很好,若想知道完整证明请左转洛谷)
可以猜测:b∈a
首先知道,a 集合能表示出的数与 b 集合能表示出的数相同。对于 b 集合中的一个数x,若 x ∉ a,则它有两种可能。第一种——无法被 a 集合中的数表示出来,则不符合题意,舍去;第二种——它可以被 a 中的比他小的一个或几个数表示出来,而用于表示它的 a 集合的那几个数,b集合中的数也可以将它们表示出来,所以b集合中的数 x 可以被 b 集合中的其他数表示出来,则它是多余的,删去它对可表示出的货币值无影响。不符合 b 集合货币种数最小的定义,舍去。
而对于一个数 y 既属于 a 集合又属于 b 集合,若它在 a 集合中可被其他数表示出,则它就是多余的,在 b 集合中不能出现。所以 b 集合中的数即为 a 集合中那些不能被其他数表示出来的数。
至于找出不能被其他数表示出的数,用完全背包即可。

代码:
#include<bits/stdc++.h>
using namespace std;const int maxn=25005;
int n,t,m,vol;
int a[maxn],f[maxn];int main(){//freopen("money.in","r",stdin);//freopen("money.out","w",stdout);scanf("%d",&t);while(t--){scanf("%d",&n);m=n;vol=0;for(int i=1;i<=n;++i) scanf("%d",&a[i]),vol=max(vol,a[i]);memset(f,0,sizeof(f));f[0]=1;for(int i=1;i<=n;++i){for(int j=a[i];j<=vol;++j){f[j]+=f[j-a[i]];}}for(int i=1;i<=n;++i) if(f[a[i]]>1) --m;printf("%d\n",m);}return 0;
}

赛道修建

思路:

分析数据约定:
当 m=1 时,转化为求树的直径:用dp找每个点可到达的最大与次大距离更新答案;或用两次dfs,从任意一点x找离它最远的点y,再从y点找离它最远的点即为树的直径。

当 ai=1 时,每条道路的起点都为同一个点,即为菊花图,所以所有赛道都由一或两条道路组成。一种方法是把所有边权记录下来,从大到小排序。设边权为 w,答案即为 (w1+w2m-1),(w2+w2m-2~)……的最小值(别问我为什么,这是洛谷 Owen_codeisking 大佬写的题解)。
还有一种蒟蒻自己写的方法 ,二分最短赛道长度 k,从小到大枚举道路 i,用lower_bound 贪心地选取最小的与 i 的长度和大于等于 k 的道路 x,并把二者打上标记,以便之后枚举到道路 x 直接跳过。每成功将两条道路组合,就将计数 tot 加一。最后用二分判断 tot 是否大于等于 m 即可。
丑陋的菊花图代码(部分):

bool check(int x){memset(v,0,sizeof(v));memcpy(w,a,sizeof(w));int tot=0,la=0;for(int i=1;i<=cnt;++i){if(!v[i]){if(la){w[i]+=la;la=0;}if(w[i]>=x){v[i]=1;if(++tot>=m) return 1;continue;}int y=lower_bound(w+1,w+1+cnt,x-w[i])-w;while(v[y]) ++y;if(w[i]+w[y]<x||y==i){la=w[i];v[i]=1;continue;}v[i]=v[y]=1;if(++tot>=m) return 1;}}return 0;
}

正解:二分+multiset
不错的 multiset 讲解博客
本题主要是用到 multiset 元素的可重复性和有序性,以便使用lower_bound贪心选取道路来组成二分出的长度。
外层二分枚举最短赛道长度 k,每个点记录两个值:tot 表示该节点的子树可组成的符合条件(长度>=k)的赛道数量;d表示该节点子树中不能组成符合条件的赛道的最长道路(注意理解)。也许不能组成符合条件的赛道的道路有多条,但回溯到该节点的祖先后,该节点的子树中可用的道路只有一个,所以用最长的道路最优。
对每个节点 u,循环枚举其子节点 v,若 d[v] 加上u、v间道路长度已经大于等于 k,则计入答案,即++tot[u] 。若不能则将其插入multiset。遍历完所有的 v 后,再依次取出multiset中的元素,即道路长度,用类似于上文中菊花图的方法,lower_bound贪心取出符合条件的道路与它组成赛道。
实际思想就是依次看每个点的子树能不能自组(用一条道路)或互组(用两条道路)出符合条件的赛道。若有不能组合的道路则选一条最长的记录下来,以便回溯后该节点的祖先在子树中组合赛道时使用。
主要的理解重点是用lower_bound贪心地选取道路组成赛道一定不会更差

代码:
#include<bits/stdc++.h>
using namespace std;const int maxn=50005;
const int inf =0x3f3f3f3f;
int n,m,cnt;
int head[maxn],tot[maxn],d[maxn];struct edge{int v,nxt,w;
}e[maxn<<1];void add(int u,int v,int w){e[++cnt].nxt=head[u];e[cnt].v=v;e[cnt].w=w;head[u]=cnt;
}void dfs(int u,int fa,int k){tot[u]=d[u]=0;multiset<int>s;for(int i=head[u];i;i=e[i].nxt){int v=e[i].v,w=e[i].w;if(v==fa) continue;dfs(v,u,k);tot[u]+=tot[v];int dis=d[v]+w;if(dis>=k) ++tot[u];else s.insert(dis);}while(!s.empty()){multiset<int>::iterator a=s.begin();int x=*a;s.erase(a);multiset<int>::iterator b=s.lower_bound(k-x);if(b==s.end()) d[u]=x;else{s.erase(b);++tot[u];}}
}bool check(int x){dfs(1,0,x);return tot[1]>=m;
}int main(){int l=inf,r=0;scanf("%d%d",&n,&m);for(int x,y,z,i=1;i<n;++i){scanf("%d%d%d",&x,&y,&z);add(x,y,z);add(y,x,z);r+=z;l=min(l,z);}int ans=0;r/=m;while(l<=r){int mid=(l+r)>>1;if(check(mid)){ans=mid;l=mid+1;}else r=mid-1;}printf("%d",ans);return 0;
}

旅行

依稀记得这道题是个基环树,但忘了该怎么做了……还是脚踏实地,像考试一样先拿部分分吧。
第一眼以为是个prim,交上去居然还有12分?
看了数据过后觉得树的情况可做,由先前交prim的代码没过才分析出每条边最多经过两次,虽然没明说,但题目中写了 “走向一个没有去过的城市,或者沿着第一次访问该城市时经过的道路后退到上一个城市” 这就表明若你经过了一条边,再退回来,就不能再次走该边了,因为先前已经走过了这条边,而这条边的节点也已访问过,这就不符合没有去过的城市后退到上一个城市 的情况。
但prim反倒启示了我,可以用一个dfs套bfs的方法。若已经过了一条边到了某个点u,就要把u的子树遍历完才能返回,否则你还未遍历完 u 的子树而返回后就不能再进来了。但在遍历 u 的子树时,可以优先挑选编号小的点遍历,这就要用到prim里的优先队列了。依次提出队顶 v,dfs进入 v 遍历下去。

这样就有60分:

void dfs(int u){ans[++tot]=u;f[find(u)]=1;priority_queue<int,vector<int>,greater<int> >q;for(int i=head[u];i;i=e[i].nxt){int v=e[i].v;if(find(v)==1) continue;q.push(v);}while(!q.empty()){dfs(q.top());q.pop();}
}

剩下40分是m=n的情况,整个图就是一个基环树。即有一个环的树。
稍微模拟一下可知基环树上的环一定有一条边不会经过,所以暴力删边n2
用vector存边以便排序。
注意函数名[滑稽]

#include<bits/stdc++.h>
using namespace std;const int maxn=5005;
int n,m,cnt,tot,cir,flag,bu,bv;
int head[maxn],vis[maxn],ans[maxn],siz[maxn],k[maxn];
vector<int>vec[maxn];struct edge
{int u,v,nxt;
}e[maxn<<1];void add(int u,int v)
{e[++cnt].nxt=head[u];e[cnt].u=u;e[cnt].v=v;head[u]=cnt;
}bool check()
{for(int i=1;i<=n;i++){if(k[i]==ans[i]) continue;if(k[i]>ans[i]) return 0;if(k[i]<ans[i]) return 1;}
}void change()
{for(int i=1;i<=n;i++){ans[i]=k[i];}
}void tp(int u,int fa)
{//priority_queue<int,vector<int>,greater<int> >q;if(vis[u]) return;vis[u]=1;k[++tot]=u;for(int i=0;i<vec[u].size();i++){int v=vec[u][i];if(v==fa) continue;if(u==bu&&v==bv) continue;if(u==bv&&v==bu) continue;//if(!vis[v]) q.push(v);tp(v,u);}}void solve(int u,int fa)
{if(vis[u]) return;vis[u]=1;ans[++tot]=u;for(int i=0;i<vec[u].size();i++){int v=vec[u][i];if(v==fa) continue;if(!vis[v]) solve(v,u);}/*while(!q.empty()){int v=q.top();q.pop();solve(v);}*/
}int main()
{//freopen("input.txt","r",stdin);//freopen("output.txt","w",stdout);scanf("%d%d",&n,&m);for(int x,y,i=1;i<=m;i++){scanf("%d%d",&x,&y);vec[x].push_back(y);vec[y].push_back(x);add(x,y); add(y,x);}for(int i=1;i<=n;i++) sort(vec[i].begin(),vec[i].end());if(n==m){for(int i=1;i<=cnt;i+=2){memset(vis,0,sizeof(vis));tot=0;int u=e[i].u,v=e[i].v;bu=u,bv=v;tp(1,0);if(tot<n) continue;if(ans[1]==0) change();else if(check()) change();}for(int i=1;i<=n;i++) printf("%d ",ans[i]);return 0;}solve(1,0);for(int i=1;i<=tot;i++) printf("%d ",ans[i]);return 0;
}

填数游戏

挖坑待填

保卫王国

详情请见洛谷Shallowy大佬的题解。
题目意思明了,就是在树形dp的基础上,要求某两个点的状态(取或不取)。

修改操作不是永久的,所以每次操作只在原有的树形dp基础上与 a,b 两座城市有关。而先前求得的dp值依然可以利用。我们需要的是一个能利用原有的dp值并快速转移的数组:
f[0/1][0/1][i][u] 表示u不选/选,u往上跳2i 步的祖先不选/选时,在除去 u 子树的dp值后,从 u 到它的祖先的最小 dp 值。之所以要除去 u 子树,就是为了在询问时方便改变 u 的dp值,使其强制选或不选。而 u 到其祖先的链上的dp值不变,故可直接使用 f 数组的值。计算答案有了倍增,时间、空间复杂度就会小很多。
询问答案时,像常规求lca一样倍增,同时利用 f 数组维护答案。主要步骤是:

  1. a 跳至 b 的深度,若 a 、b相遇则跳过下一步。
  2. a、b 一起向上跳直到相遇,若一起跳到了根节点则跳过下一步。
  3. a、b看做一个点,一直向上跳直到根节点。

关键之处在于,每次当一个点 u 要跳到目标 v 节点时,要用 v 原有的dp值减去包含 u 的子树的dp值,再加入先前这条路径上算出的答案。因为在 u 向上跳的过程中答案已经将它的跳跃路径上的值统计了,不能再把原有的dp值重复加进来。

至于强制某个点选或不选,则是通过给初始答案赋极大值的方法,只给正确的选择方式赋值,这样一定会用正确的选择来更新答案。

本题需要熟练地运用倍增,考虑各种情况,但我还远远达不到这个要求。

这道题的解法实在是很难说明,我自己的讲解确实不甚明了。本蒟蒻还需要加深理解啊。

#include<bits/stdc++.h>
using namespace std;typedef long long ll;
const ll maxn=1e5+7;
const ll inf=1e15+7;
ll n,m,cnt;
ll head[maxn],w[maxn],d[maxn],t[maxn][30];
ll f[maxn][30][2][2],dp[maxn][2];
char s[5];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;
}struct edge{ll v,nxt;
}e[maxn<<1];void add(ll u,ll v){e[++cnt].nxt=head[u];e[cnt].v=v;head[u]=cnt;
}
void dfs1(int u,int _fa) {d[u] = d[_fa]+1; t[u][0] = _fa; dp[u][1] = w[u];for(int i = 1;i <= 17; ++i) t[u][i] = t[t[u][i-1]][i-1];for(int i = head[u]; i;i = e[i].nxt) {int v = e[i].v; if(v == _fa) continue;dfs1(v,u);dp[u][0] += dp[v][1]; dp[u][1] += min(dp[v][0],dp[v][1]);}
}void dfs1(ll u,ll fa){d[u]=d[fa]+1;t[u][0]=fa;dp[u][1]=w[u];for(ll i=1;(1<<i)<=d[u];++i) t[u][i]=t[t[u][i-1]][i-1];for(ll i=head[u];i;i=e[i].nxt){ll v=e[i].v;if(v==fa) continue;dfs1(v,u);dp[u][1]+=min(dp[v][1],dp[v][0]);dp[u][0]+=dp[v][1];}
}void dfs2(ll u,ll fa){f[u][0][0][0]=inf;f[u][0][1][0]=dp[fa][0]-dp[u][1];f[u][0][1][1]=f[u][0][0][1]=dp[fa][1]-min(dp[u][1],dp[u][0]);for(ll i=1;(1<<i)<=d[u];++i){f[u][i][0][0]=min(f[u][i-1][0][0]+f[t[u][i-1]][i-1][0][0],f[u][i-1][0][1]+f[t[u][i-1]][i-1][1][0]);//通过枚举中间节点选或不选来维护出f值f[u][i][0][1]=min(f[u][i-1][0][0]+f[t[u][i-1]][i-1][0][1],f[u][i-1][0][1]+f[t[u][i-1]][i-1][1][1]);f[u][i][1][0]=min(f[u][i-1][1][0]+f[t[u][i-1]][i-1][0][0],f[u][i-1][1][1]+f[t[u][i-1]][i-1][1][0]);f[u][i][1][1]=min(f[u][i-1][1][0]+f[t[u][i-1]][i-1][0][1],f[u][i-1][1][1]+f[t[u][i-1]][i-1][1][1]);}for(ll i=head[u];i;i=e[i].nxt){if(e[i].v!=fa) dfs2(e[i].v,u);}
}void work(int a,bool x,int b,bool y) {if(d[a] < d[b]) swap(a,b),swap(x,y);ll a0,a1,b0,b1,l0,l1,L,ans;a0 = a1 = b0 = b1 = l0 = l1 = ans = inf;x ? a1 = dp[a][1] : a0 = dp[a][0]; y ? b1 = dp[b][1] : b0 = dp[b][0];for(int i = 17;i >= 0; --i) {if(d[t[a][i]] < d[b]) continue;ll t0 = a0,t1 = a1;a0 = min(t0+f[a][i][0][0],t1+f[a][i][1][0]);a1 = min(t0+f[a][i][0][1],t1+f[a][i][1][1]);a = t[a][i];}if(a == b) L = a,y ? l1 = a1 : l0 = a0;else {for(int i = 17;i >= 0; --i) {if(t[a][i] == t[b][i]) continue;ll t0 = a0,t1 = a1,p0 = b0,p1 = b1;a0 = min(t0+f[a][i][0][0],t1+f[a][i][1][0]);a1 = min(t0+f[a][i][0][1],t1+f[a][i][1][1]);b0 = min(p0+f[b][i][0][0],p1+f[b][i][1][0]);b1 = min(p0+f[b][i][0][1],p1+f[b][i][1][1]);a = t[a][i]; b = t[b][i];}L = t[a][0];l0 = dp[L][0]-dp[a][1]-dp[b][1]+a1+b1;l1 = dp[L][1]-min(dp[a][0],dp[a][1])-min(dp[b][0],dp[b][1])+min(a0,a1)+min(b0,b1);}if(L == 1) ans = min(l0,l1);else {for(int i = 17;i >= 0; --i) {if(d[t[L][i]] < 2) continue;ll t0 = l0,t1 = l1;l0 = min(t0+f[L][i][0][0],t1+f[L][i][1][0]);l1 = min(t0+f[L][i][0][1],t1+f[L][i][1][1]);L = t[L][i];}ans = min(dp[1][0]-dp[L][1]+l1,dp[1][1]-min(dp[L][0],dp[L][1])+min(l0,l1));}if(ans<inf) printf("%lld\n",ans);else printf("-1\n");
}int main(){//freopen("input.txt","r",stdin);freopen("defense.in","r",stdin);freopen("defense.out","w",stdout);n=read(),m=read();scanf("%s",s);for(ll i=1;i<=n;++i) w[i]=read();for(ll x,y,i=1;i<n;++i){x=read(),y=read();add(x,y),add(y,x);}dfs1(1,0);dfs2(1,0);ll a,x,y,b;while(m--){x=read(),a=read(),y=read(),b=read();work(x,a,y,b);}return 0;
}

noip 2018 做题记录相关推荐

  1. 退役前的做题记录5.0

    退役前的做题记录5.0 出于某种原因新开了一篇. [CodeChef]Querying on a Grid 对序列建立分治结构,每次处理\((l,mid,r)\)时,以\(mid\)为源点建立最短路树 ...

  2. 退役前的做题记录4.0

    退役前的做题记录4.0 最近主要在LOJ上写题 536. 「LibreOJ Round #6」花札 比较显然的二分图博弈模型,先手必胜当且仅当起始点一定在最大匹配中.连边可以对每种颜色以及数字建一个点 ...

  3. 2020.9月做题记录

    八月的做题记录因为是暑假所以鸽掉了. 离联赛真的不远了,要继续努力啊qwq- week -1 2020.08.30 2020.08.30 今天考试,修了20+次锅,修的我都没有心情做题了- 然后开始消 ...

  4. 概率期望题(期望 DP)做题记录

    概率期望题(期望 DP)做题记录 P3830 [SHOI2012]随机树 难点在于第二问:生成树的期望深度. 不 wei zhuo 捏,设 \(dp_{i,j}\) 表示已经有了 \(i\) 个叶子结 ...

  5. 数数题(计数类 DP)做题记录

    数数题(计数类 DP)做题记录 CF1657E Star MST 我们称张无向完全图是美丽的当且仅当:所有和 \(1\) 相连的边的边权之和等于这张完全图的最小生成树的边权之和. 完全图点数为 \(n ...

  6. CSDN 第六期编程竞赛做题记录

    CSDN 第六期编程竞赛做题记录 -- CSDN编程竞赛报名地址:https://edu.csdn.net/contest/detail/16 9.18周日闲来无视写一下 csdn 的编程题,每期编程 ...

  7. Regional 做题记录 (50/50)

    写在前面 博主深感自己太弱了QAQ 于是有了一个刷水的想法,Regional的题目还是有很多考查思维的题目,所以这次是乱做50道思考题,可能会顺带做一些水题,这些题的简要题解会写到这篇博文里面,希望能 ...

  8. 2020.7月做题记录

    转眼就到了2020的下半年了-前方仍是一片茫然. 长期计划 prufer 序列 2020.07.02-2020.07.04 Problem Finished P2624 [HNOI2008]明明的烦恼 ...

  9. 退役前的做题记录1.0

    退役前的做题记录1.0 租酥雨最近很懒qwq,具体表现在写题的时候不想发题解了. 但是想想这样也不太好,就决定发个一句话(半句话到几句话不等)题解上来. 2018-09.18-2018-09.28 [ ...

最新文章

  1. Linux查找文件内容(grep)
  2. 将 Smart 构件发布到 Maven 中央仓库
  3. spring中的bean属性相关访问、编辑、转换
  4. 1.20 正则表达式详解
  5. php图形界面框架,python GUI 图形化界面框架的选择
  6. Windows程序闪退Windows日志捕获Kernelbase模块错误
  7. jQuery选择器种类整理
  8. spring数据字典_Redis为什么默认16个数据库?
  9. UI设计素材干货|可临摹的时尚播放页面模板
  10. Visual Studio Code 1.49 发布
  11. [考试]20150903
  12. mysql —— 分表分区(1)
  13. android判断正确密码,Android 监听EditText输入框 ,判断输入的密码是什么格式
  14. mysql数据库计算全部女生_数据分析mysql入门到精通(1)
  15. Microsoft Softwares
  16. Inter Edsion添加USB有线网卡解决办法
  17. oracle12c不使用cdb模式,12c CDB和PDB启动和关闭操作
  18. 小米路由器3G刷入OpenWrt
  19. 多家软件厂商卷入360与腾讯之争
  20. c调用c++:opencv c版本打开相机方法

热门文章

  1. zjoi 2008 杀蚂蚁
  2. switch语句使用时注意事项
  3. GDUT_专题二_C - 开餐馆
  4. 今天有个想法,使用触觉代替视觉实现盲人视觉。
  5. 在macOS上使用网易mumu模拟器和触动精灵
  6. 反爬虫?来了解下这个爬虫终结者!
  7. SpringBoot 重定向和转发
  8. 基于ESP32CAM的手机app控制的图传小车
  9. 解决git配置多个SSH公钥的问题
  10. 爬取豆瓣影评,告诉你都挺好这部家庭伦理剧发生了什么