1、22-构造数组
解题思路:
观察可以发现b数组始终是大于a数组的,而两个数组中的每个数都是从1-n中选择的,可以重复。
因此
第一步:从1-n中选择2m个数
第二部:将这2m个数排序,大的m个数给b,小的m个数给a ------一种方案
答案就是从1-n中选择2m个数有多少种选择方案。
转化一:设x1,x2,x3,…xn表示这n个数在一轮选择中被选中的次数,即有
x1+x2+x3+…+xn = 2m,那么这个不定方程解的个数就是答案
转化二:设x’i = xi+1;
x’1 + x’2 + … + x’n = 2m+n;
转化三:隔板法
这时可以处理成有2m+n个小球,用n-1个板子将其隔开,有多少种选法,答案是C2m+n-12m种
#include#include #include using namespace std; typedef long long LL; const int mod = 1e9+7; const int N = 2010; int n,m; int c[N][N]; void init() { for (int i = 0; i < N; i ++ ) for (int j = 0; j <= i; j ++ ) if (!j) c[i][j] = 1; else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; } int main() { cin >> n >> m; init(); cout << c[2*m+n-1][2*m] <<"n"; return 0; }
2、13-最大路径权值
解题思路:拓扑排序、dp。
(1)考虑正无穷的情况,那就是出现环了。有向无环图判断环就是拓扑排序的结果小于n
(2)根据拓扑排序结果从后往前推,f[i][j]表示从第i个点出发的所有路径中值为j的个数的最大值。
(3)遍历所有的节点,取max(f[i][j])
#include#include #include #include #include using namespace std; const int N = 3e5+10, M = 26; int f[N][M]; char a[N]; vector path; // 拓扑排序的结果 int n,m; int h[N], e[N], ne[N], idx; int din[N]; void add(int a,int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } bool topo() { queue q; for(int i = 1; i <= n; i ++) { if(din[i] == 0) q.push(i); } int res = 0; while(q.size()) { int t =q.front(); q.pop(); res ++; path.push_back(t); for(int i = h[t];~i;i=ne[i]) { int j = e[i]; din[j] --; if(din[j] == 0) q.push(j); } } if(res < n) return false; return true; } int main() { ios::sync_with_stdio(false); cin.tie(0); memset(h, -1, sizeof h); cin >> n >> m; for (int i = 1; i <= n; i ++ ) cin >> a[i]; while (m -- ) { int a,b; cin >> a >> b; add(a,b); din[b]++; } if(!topo()) cout << "-1" << "n"; else { for(int i = n-1; i >= 0; i --) { int ver = path[i]; for(int j = h[ver];~j;j=ne[j]) { int k = e[j]; for(int s = 0; s < 26; s ++) { f[ver][s] = max(f[ver][s],f[k][s]); } } f[ver][a[ver]-'a'] ++; } int ans = 0; for (int i = 1; i <= n; i ++ ) { for(int j = 0; j < 26; j ++) { ans = max(ans,f[i][j]); } } cout << ans << "n"; } return 0; }
3、11-最大化最短路
题意概括:给定一个无向图,还有一群特殊点,选择其中两个点相连,使得1-n的最短路最大。
解题思路: bfs,贪心
(1)假设选择了特殊点中的两个点i,j,那么最短距离为dist1[i]+dist2[j]+1的最大值与不连点之间的最小值。
(2)对于i,j来说,当dist1[i]+dist2[j] <= dist1[j]+dist2[i]->dist1[i]-dist1[j] <= dist2[i]-dist2[j],按照这个规则进行排序,然后枚举每个特殊点
#include#include #include #include using namespace std; const int N = 2e5+10, M = 4e5+10; int h[N],e[M],ne[M],idx; int n,m,k; int s[N]; int dist1[N],dist2[N]; void add(int a,int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } void bfs(int u,int dist[]) { memset(dist,0x3f,N*4); queue q; q.push(u); dist[u] = 0; while(q.size()) { int t = q.front(); q.pop(); for(int i = h[t];~i;i=ne[i]) { int j = e[i]; if(dist[j] > dist[t]+1) { dist[j] = dist[t]+1; q.push(j); } } } } bool cmp(int a,int b) { return dist1[a]-dist2[a] < dist1[b] - dist2[b]; } int main() { memset(h, -1, sizeof h); cin >>n >> m >> k; for (int i = 1; i <= k; i ++ ) cin >> s[i]; while (m -- ) { int a,b; cin >> a >> b; add(a, b); add(b, a); } bfs(1,dist1); bfs(n,dist2); sort(s+1,s+1+k,cmp); int maxx = dist1[s[1]],res = 0; for(int i = 2;i <= k; i ++) { int j = s[i]; res = max(res,maxx+dist2[j]+1); maxx = max(maxx,dist1[j]); } res = min(res,dist1[n]); cout << res << "n"; return 0; }
4、8-选取石子
解题思路:思维
(1)因为a[i] > 0,所以对于一个集合里的元素来讲,越多越好
(2)对于能够放入一个集合的元素要满足 ax-ay = x-y,移项之后可得ax-x = ay-y,于是可以用哈希表将值与坐标差值相同的项求和,同时与数组中最大的元素取最大值。
#include#include #include #include using namespace std; typedef long long LL; const int N = 2e5+10; int n; int a[N]; unordered_map mp; int main() { cin >> n; LL ans = 0; for (int i = 1; i <= n; i ++ ) { LL x; cin >> x; ans = max(ans,x); mp[x-i] += x; } for(auto &[x,y] : mp) { ans = max(ans,y); } cout << ans << "n"; return 0; }
5、8-更新路线
解题思路:
对于给定的路线,假设路线为 a->b,dist表示当前点到终点的最短距离。分两种情况,如果dist[a] < dist[b]+1,那么就必须更新路线。否则 如果从a到终点的路线不止一条,那么此时可以更新也可以不更新。maxc++
(1)反向建边,求得终点到各个点的最短距离,同时记录下每个点到终点有多少条最短路
(2)遍历待走的点,如果dist[a] < dist[b]+1,那么必须更新。否则判断cnt[a] > 1,那么maxc++
#include#include #include #include using namespace std; const int N = 2e5+10, M = N; int h[N], e[M], ne[M], idx; int n,m,k; int path[N],dist[N],cnt[N]; void add(int a, int b) // 添加一条边a->b { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } void bfs(int u) { memset(dist,0x3f,sizeof dist); dist[u] = 0; queue q; q.push(u); while(q.size()) { int t = q.front(); q.pop(); for(int i = h[t];~i;i = ne[i]) { int j = e[i]; if(dist[j] > dist[t] + 1) { dist[j] = dist[t] + 1; cnt[j] = 1; q.push(j); } else if(dist[j] == dist[t] + 1) cnt[j]++; } } } int main() { memset(h, -1, sizeof h); cin >> n >> m; while (m -- ) { int a,b; cin >> a >> b; add(b, a); // 反向建边 } cin >> k; for (int i = 1; i <= k; i ++ ) cin >> path[i]; bfs(path[k]); int maxc = 0, minc = 0; for(int i = 1; i < k; i ++) { int a = path[i],b = path[i+1]; if(dist[a] < dist[b] + 1) minc++,maxc++; else if(cnt[a] > 1) maxc++; } cout << minc <<" " << maxc << "n"; return 0; }
6、7-最大剩余油量
解题思路:树形dp
枚举每个以节点i为子树的最大权值和次大权值,走这两条路+当前节点的油量。最后取最大值就行
#include#include #include using namespace std; typedef long long LL; const int N = 3e5+10, M = N*2; int n; int v[N]; LL ans = 0; int h[N], e[M], ne[M], w[M],idx; void add(int a, int b, int c) // 添加一条边a->b,边权为c { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } LL dfs(int u,int fa) { LL d1 = 0,d2 = 0; for(int i = h[u];~i;i = ne[i]) { int j = e[i]; if(j == fa) continue; LL d = dfs(j,u); if(d < w[i]) continue; d -= w[i]; if(d >= d1) d2 = d1,d1 = d; else if(d > d2) d2 = d; } ans = max(ans,d1+d2+v[u]); return d1+v[u]; } int main() { memset(h, -1, sizeof h); cin >> n; for (int i = 1; i <= n; i ++ ) cin >> v[i]; for(int i = 1; i < n; i ++) { int a,b,c; cin >> a >> b >> c; add(a, b, c); add(b, a, c); } dfs(1,-1); cout << ans << "n"; return 0; }
7、4-构造有向无环图
题意概括:给定一部分有向边和无向边,要求给定无向边方向,使得整个图是一个DAG图
解题思路:
(1)在建边的时候先不管无向边,只连接有向边
(2)判断这个图的拓扑排序行不行
(3)然后根据拓扑排序的顺序,无向边的方向就是从前一个连向后一个
#include#include #include #include using namespace std; typedef pair PII; const int N = 2e5+10, M = N; int T,n,m; vector path; PII alls[N]; int din[N]; int pos[N]; int h[N], e[M], ne[M], idx; void add(int a, int b) // 添加一条边a->b { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } void init() { for(int i = 1; i <= n; i ++) { pos[i] = 0; h[i] = -1; din[i] = 0; } idx = 0; path.clear(); } bool topo() { queue q; for(int i = 1; i <= n; i ++) { if(din[i] == 0) q.push(i); } int res = 0; while(q.size()) { int t = q.front(); q.pop(); res++; path.push_back(t); for(int i = h[t];~i;i=ne[i]) { int j = e[i]; din[j] --; if(din[j] == 0) q.push(j); } } if(res < n) return false; return true; } void Print() { for(int i = 0; i < n; i ++) pos[path[i]] = i; for (int i = 1; i <= m; i ++ ) { int a = alls[i].first,b = alls[i].second; if(pos[a] < pos[b]) cout << a <<" " << b << "n"; else cout << b << " " << a << "n"; } } int main() { memset(h, -1, sizeof h); cin >> T; while(T --) { cin >> n >> m; for(int i = 1; i <= m; i ++) { int type,a,b; cin >> type >> a >> b; if(type == 1) add(a,b),din[b]++; alls[i] = {a,b}; } if(topo()) { cout << "YES" << "n"; Print(); } else cout << "NO" << "n"; init(); } return 0; }
3、最大上升子序列和
解题思路:
(1)f[i]表示以i结尾的最长上升子序列和的最大值。f[i] = max(f[0-i-1])+a[i]。
(2)对于求解0~i-1中 < a[i]的所有元素的最大值可以用树状数组来进行动态维护
(3)先进行离散化,对于个a[i],更新树状数组和最大值
动态的维护满足小于a[i]的前缀最大值可以用树状数组来求解
#include#include #include #include using namespace std; const int N = 1e5+10; typedef long long LL; LL f[N]; int n; int a[N]; vector alls; LL tr[N]; int get(int x) { return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1; } int lowbit(int x) { return x&(-x); } void add(int x,LL v) { for(int i = x; i <= n; i += lowbit(i)) tr[i] = max(tr[i],v); } LL query(int x) { LL res = 0; for(int i = x; i; i -= lowbit(i)) res = max(res,tr[i]); return res; } int main() { cin >> n; for (int i = 1; i <= n; i ++ ) { int x; cin >> x; a[i] = x; alls.push_back(x); } sort(alls.begin(),alls.end()); alls.erase(unique(alls.begin(),alls.end()),alls.end()); LL ans = 0; for (int i = 1; i <= n; i ++ ) { int idx = get(a[i]); f[i] = query(idx-1)+a[i]; ans = max(ans,f[i]); add(idx,f[i]); } cout << ans << "n"; return 0; }



