题解摘自各个博客(来源也不是很想复制了,懒人一枚),自己不想打了,稍作补充
4985: 评分
Time Limit: 10 Sec Memory Limit: 64 MBDescription
Input
Output
Sample Input
5 2
5 5
8 6
6
2
8
9
首先二分一个答案x,然后我们把>=x的数看成1,<x的数看成0,那如果最后剩下1,这个答案就是合法的。
那我们就来算让某一位得1至少需要填几个1(设这个值是f[i])
i=1..n时,显然,如果i已经固定,f[i]=0或inf(取决于原来是1还是0);如果i还没有固定,那f[i]=1
然后每次就可以由前三个转移到最后一个,也就是取这三个中f[i]较小的两个相加(转移过去的是1,当且仅当3个里有至少2个1)
这个转移和队列很像,所以可以直接用队列维护。
最后我们看f[最后那位数]是否多于还没填的1的数量就完事了。
#include<bits/stdc++.h> using namespace std; const int ME = 200005, M = 1e5 + 5; queue <int> q; int inf = 1e9, f[M], m, sc[M], d[M], n, cc[M]; bool check(int x){ int tot = 0; for(int i = 1; i <= n-m; i++) tot += cc[i] >= x; for(int i = 1; i <= n; i++){ if(sc[i]) f[i] = sc[i] >= x ? 0 : inf; else f[i] = 1; q.push(f[i]); } while(!q.empty()){ int a=q.front();q.pop(); if(q.empty()) return a <= tot; int b=q.front();q.pop(); int c=q.front();q.pop(); q.push(min(inf, min(a+b, min(b+c, a+c)))); } } int main(){ int p; scanf("%d%d", &n, &m); for(int i = 1; i <= m; i++) scanf("%d%d", &d[i], &p), sc[p] = d[i]; for(int i = m+1; i <= n; i++) scanf("%d", &d[i]), cc[i-m] = d[i]; sort(d+1, d+1+n); int ans, lf = 1, rg = n; while(lf <= rg){ int mid = (lf + rg) >> 1; if(check(d[mid])) ans = d[mid], lf = mid + 1; else rg = mid - 1; } printf("%d ", ans); }
2131: 免费的馅饼
Time Limit: 10 Sec Memory Limit: 259 MBDescription
Input
第一行是用空格隔开的二个正整数,分别给出了舞台的宽度W(1到10^8之间)和馅饼的个数n(1到10^5)。 接下来n行,每一行给出了一块馅饼的信息。由三个正整数组成,分别表示了每个馅饼落到舞台上的时刻t[i](1到10^8秒),掉到舞台上的格子的编号p[i](1和w之间),以及分值v[i](1到1000之间)。游戏开始时刻为0。输入文件中同一行相邻两项之间用一个空格隔开。输入数据中可能存在两个馅饼的t[i]和p[i]都一样。
Output
一个数,表示游戏者获得的最大总得分。
Sample Input
1 2 3
5 2 3
6 3 4
1 1 5
Sample Output
【数据规模】
对于100%的数据,1<=w,t[i]<=10^8,1<=n<=100000。
题解:两个要点:一是把时间增倍,可以看成走一步,休息一步;
二是一个二维偏序的转移
一道经典的二维偏序问题。至于怎么将原题目转换为二维偏序??
首先可以将每秒走一步和两步转换为在一秒钟走一步或半步,即把时间加倍。接下来考虑dp转移。能从j转移到i当且仅当ti-tj>=|pi-pj|,可以转换为两个式子:pi>=pj时,ti-pi>=tj-pj,又因为pi-pj此时是正数,所以pj-pi是负数,因为ti-tj此时已经大于一个正数,则它也一定大于负数,即ti-tj>=pj-pi也成立,即ti+pi>=tj+pj一定成立,同理pi<pj时,ti+pi>=tj+pj,ti-pi>=tj-pj也一定成立。所以满足条件的转移一定满足这两个式子。而满足这两个式子时,ti-tj一定是个正数。所以不用考虑ti的顺序了。
设val1=ti+pi,val2=ti-pi,则转换为了一个二维偏序问题。一维排序,一维用值域树状数组或者值域线段树优化。【注意】因为t值非常大,需要离散化值域
#include<bits/stdc++.h> using namespace std; const int M = 1e5 + 5; int w, n, lim, ls[M], c[M]; struct Cake{ int x, y, v; }s[M]; bool cmp(Cake a, Cake b){ return a.x < b.x; } int query(int x){ int ret = 0; for(; x; x -= x&(-x)) ret = max(ret, c[x]); return ret; } void update(int x, int v){ for(; x <= lim; x += x&(-x)) c[x] = max(c[x], v); } int main(){ int p, t, v; scanf("%d%d", &w, &n); for(int i = 1; i <= n; i++){ scanf("%d%d%d", &t, &p, &v); s[i].x = 2*t + p; s[i].y = 2*t - p; s[i].v = v; ls[i] = s[i].y; } sort(s + 1, s + 1 + n, cmp); sort(ls + 1, ls + 1 + n); lim = unique(ls + 1, ls + 1 + n) - ls - 1; for(int i = 1; i <= n; i++){ int pos = lower_bound(ls + 1, ls + 1 + lim, s[i].y) - ls; int now = query(pos) + s[i].v; update(pos, now); } int ans = query(lim); printf("%d ", ans); }
1975: [Sdoi2010]魔法猪学院
Time Limit: 10 Sec Memory Limit: 64 MBDescription
Input
Output
Sample Input
1 2 1.5
2 1 1.5
1 3 3
2 3 1.5
3 4 1.5
1 4 1.5
Sample Output
HINT
样例解释
有意义的转换方式共4种:
1->4,消耗能量 1.5
1->2->1->4,消耗能量 4.5
1->3->4,消耗能量 4.5
1->2->3->4,消耗能量 4.5
显然最多只能完成其中的3种转换方式(选第一种方式,后三种方式仍选两个),即最多可以转换3份样本。
如果将 E=14.9 改为 E=15,则可以完成以上全部方式,答案变为 4。
数据规模
占总分不小于 10% 的数据满足 N <= 6,M<=15。
占总分不小于 20% 的数据满足 N <= 100,M<=300,E<=100且E和所有的ei均为整数(可以直接作为整型数字读入)。
所有数据满足 2 <= N <= 5000,1 <= M <= 200000,1<=E<=107,1<=ei<=E,E和所有的ei为实数。
题解:贪心+A*,相当于先走最短的;
#include<bits/stdc++.h> using namespace std; const int ME = 200005, M = 5005; int n, m; int tot, tot1, h[M], hh[M]; bool inq[M]; struct edge{int v, nxt;double w;}G[ME], g[ME]; void add(int u, int v, double w){G[++tot].v = v, G[tot].w = w, G[tot].nxt = h[u], h[u] = tot;} void add_op(int u, int v, double w){g[++tot1].v = v, g[tot1].w = w, g[tot1].nxt = hh[u], hh[u] = tot;} double dis[M], E, inf = 2e9; queue<int> Q; void spfa(){ for(int i=1;i<=n;i++)dis[i]=inf; Q.push(n);inq[n]=1;dis[n]=0; while(!Q.empty()){ int u=Q.front();Q.pop();inq[u]=0; for(int i=hh[u];i;i=g[i].nxt){ int v=g[i].v; if(dis[v] > dis[u]+g[i].w){ dis[v] = dis[u]+g[i].w; if(!inq[v])inq[v]=1, Q.push(v); } } } } struct node{ int v; double f; bool operator < (const node &rhs)const{ return f + dis[v] > rhs.f + dis[rhs.v]; } }; priority_queue<node> q; int Astar(){ int k = 0; q.push((node){1, 0}); while(!q.empty()){ node u=q.top();q.pop(); if(u.v == n){ if(u.f > E)break; E -= u.f; k++; } for(int i=h[u.v];i;i=G[i].nxt){ int v=G[i].v; q.push((node){v, u.f + G[i].w}); } } return k; } int main(){ int u, v; double w; scanf("%d%d%lf",&n, &m, &E); for(int i = 1; i <= m; i++){ scanf("%d%d%lf", &u, &v, &w); add(u, v, w); add_op(v, u, w); } spfa(); int ans = Astar(); printf("%d ", ans); }
3697: 采药人的路径
Time Limit: 10 Sec Memory Limit: 128 MBDescription
采药人的药田是一个树状结构,每条路径上都种植着同种药材。
采药人以自己对药材独到的见解,对每种药材进行了分类。大致分为两类,一种是阴性的,一种是阳性的。
采药人每天都要进行采药活动。他选择的路径是很有讲究的,他认为阴阳平衡是很重要的,所以他走的一定是两种药材数目相等的路径。采药工作是很辛苦的,所以他希望他选出的路径中有一个可以作为休息站的节点(不包括起点和终点),满足起点到休息站和休息站到终点的路径也是阴阳平衡的。他想知道他一共可以选择多少种不同的路径。
Input
第1行包含一个整数N。
接下来N-1行,每行包含三个整数a_i、b_i和t_i,表示这条路上药材的类型。
Output
输出符合采药人要求的路径数目。
Sample Input
1 2 0
3 1 1
2 4 0
5 2 0
6 3 1
5 7 1
Sample Output
HINT
对于100%的数据,N ≤ 100,000。
题解:一道好题,一个思维与码力兼具的题
来自出题人hta的题解。。
本题可以考虑树的点分治。问题就变成求过根满足条件的路径数。
路径上的休息站一定是在起点到根的路径上,或者根到终点的路径上。
如何判断一条从根出发的路径是否包含休息站?只要在dfs中记录下这条路径的和x,同时用个标志数组判断这条路径是否存在前缀和为x的节点。
这样我们枚举根节点的每个子树。用f[i][0…1],g[i][0…1]分别表示前面几个子树以及当前子树和为i的路径数目,0和1用于区分路径上是否存在前缀和为i的节点。那么当前子树的贡献就是f[0][0] * g[0][0] + Σf [i][0] * g [-i][1] + f[i][1] * g[-i][0] + f[i][1] * g[-i][1],其中i的范围[-d,d],d为当前子树的深度。
#include<bits/stdc++.h> using namespace std; #define ex(i, u) for(int i = h[u]; i; i = G[i].nxt) #define ll long long const int M = 100005, N = M; int h[M], dep[M], tot, dis[M], f[M << 1][2], g[M << 1][2], siz[M], son[M], tmp[M], root, sum, ap[N*2], mxdep; ll Ans; bool vis[M]; int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();} return x*=f; } struct edge{int v, w, nxt;}G[M << 1]; void add(int u, int v, int w){G[++tot].v = v, G[tot].w = w, G[tot].nxt = h[u], h[u] = tot;} void getroot(int u, int f){ siz[u] = 1; son[u] = 0; ex(i, u){ int v = G[i].v; if(vis[v] || v == f)continue; getroot(v, u); siz[u] += siz[v]; if(siz[son[u]] < siz[v]) son[u] = v; } tmp[u] = max(siz[son[u]], sum - siz[u]); if(tmp[u] < tmp[root]) root = u; } void getdeep(int u, int f){ mxdep = max(mxdep ,dep[u]); if(ap[dis[u]])g[dis[u]][1]++; else g[dis[u]][0]++; ap[dis[u]]++; ex(i, u){ int v = G[i].v; if(v == f || vis[v])continue; dis[v] = dis[u] + G[i].w; dep[v] = dep[u] + 1; getdeep(v, u); } ap[dis[u]]--; } ll cal(int u, int kk){ dep[u] = 1; dis[u] = kk + N; ll ret = 0; mxdep = 1; f[N][0] = 1; ex(i, u){ int v = G[i].v; if(vis[v])continue; dis[v] = dis[u] + G[i].w; dep[v] = dep[u] + 1; getdeep(v, u); ret += g[N][0] * (f[N][0] - 1); for(int j = -mxdep; j <= mxdep; j++) ret += f[N + j][0] * g[N - j][1] + f[N + j][1] * (g[N - j][1] + g[N - j][0]); //printf("%d %d ", j, ret); for(int j = N - mxdep; j <= N + mxdep; j++){ f[j][0] += g[j][0], f[j][1] += g[j][1]; g[j][0] = g[j][1] = 0; } } for(int i = N - mxdep; i <= N + mxdep; i++)f[i][0] = f[i][1] = 0; return ret; } void dfs(int u){ Ans += cal(u, 0); //printf("+ %I64d %d ", Ans, u); vis[u] = 1; ex(i, u){ int v = G[i].v; if(vis[v])continue; //Ans -= cal(v, G[i].w); //printf("- %I64d %d ", Ans, v); sum = siz[v]; getroot(v, root = 0); dfs(root); } } int main(){ //freopen("data.out","r",stdin); //freopen("my.out","w",stdout); //int tt = clock(); int n = read(); int u, v, w; for(int i = 1; i < n; i++){ u = read(), v = read(), w = read(); w = w ? 1 : -1; add(u, v, w), add(v, u, w); } tmp[0] = 1e9; sum = n; getroot(1, 0); dfs(root); printf("%lld ", Ans); //int cc = clock(); //cout<<cc-tt; }
4565: [Haoi2016]字符合并
Time Limit: 20 Sec Memory Limit: 256 MBDescription
Input
Output
输出一个整数表示答案
Sample Input
101
1 10
1 10
0 20
1 30
Sample Output
//第3行到第6行表示长度为2的4种01串合并方案。00->1,得10分,01->1得10分,10->0得20分,11->1得30分。
题解:困难的区间状压DP,目前应该可以放一放
发现一个区间的最大分数一定是压缩到最短的时候,这个时候的长度是(modk−1)(modk−1)下的长度,设f[l][r][o]f[l][r][o]表示[l,r][l,r]区间压缩为oo状态的最大分数,那么转移只需考虑把原始区间分为两段,两段拼接成oo状态就好了。
但是如何DP?有一种思路是直接枚举区间与状态,然后两段的状态随之确定。但是有一个问题是可能是两段的状态合并之后再压缩成当前的状态。其实仔细分析根本不用考虑合并的状态,只需把转移锁定到状态的最后一位即可:
1.若该区间压缩后的长度不为1,那么该区间的最后状态o1o1的最后一位一定是由该区间末尾的某一段压缩而成。直接枚举是哪一段就好了。
2.若该区间压缩后的长度为1,那么考虑枚举长度为k的状态,之后再枚举一遍每种状态压缩后的分数,取最大值即可。
#include<bits/stdc++.h> using namespace std; const int M = 305, N = (1 << 8) + 1; #define ll long long int a[M], to[N]; ll w[N], dp[M][M][N], g[3]; const ll inf = -1e9; int main(){ int n, k; scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++)scanf("%1d", &a[i]); for(int s = 0; s < (1 << k); s++)scanf("%d%lld", &to[s], &w[s]); memset(dp, 0x8f, sizeof(dp)); for(int i = 1; i <= n; i++)dp[i][i][a[i]] = 0; for(int L = 2; L <= n; L++) for(int i = 1; i <= n - L + 1; i++){ int j = i + L - 1, len = j - i; while(len > k - 1) len -= (k - 1); for(int mid = j; mid > 0; mid -= k-1){ for(int s = 0; s < (1 << len); s++) if(dp[i][mid - 1][s] > inf){ if(dp[mid][j][1] > inf) dp[i][j][s<<1|1] = max(dp[i][j][s<<1|1], dp[i][mid - 1][s] + dp[mid][j][1]); if(dp[mid][j][0] > inf) dp[i][j][s<<1] = max(dp[i][j][s<<1], dp[i][mid - 1][s] + dp[mid][j][0]); //printf("%d %d %d %d %I64d %I64d ",len,i, j, s, dp[i][j][s<<1|1], dp[i][j][s<<1]); } } if(len == k-1){ g[0] = g[1] = inf; for(int s = 0; s < (1 << k); s++) if(dp[i][j][s] > inf) g[to[s]] = max(g[to[s]], dp[i][j][s] + w[s]); dp[i][j][1] = g[1]; dp[i][j][0] = g[0]; //printf("%d %d %I64d %I64d twice ",i, j, dp[i][j][0], dp[i][j][1]); } } ll ans = inf; for(int s = 0; s < (1 << k); s++) ans = max(ans, dp[1][n][s]); printf("%lld ", ans); }