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<int> 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<int> 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<int> 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<int,LL> 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<int> 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<int, int> PII;
const int N = 2e5+10, M = N;
int T,n,m;
vector<int> 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<int> 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<int> 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;
}