A
二维dp[i][j]表示第i位选择j情况(0/1)的所有方案数。
#include<iostream>
using namespace std;
const int N=1e5+10,mod=5000011;
int f[N],n,k;
void solve()
{
cin>>n>>k;
for(int i=1;i<=k+1;i++) f[i]=i+1;
for(int i=k+2;i<=n;i++)
{
f[i]=f[i-k-1]+f[i-1];
f[i]%=mod;
}
cout<<f[n]<<endl;
}
int main()
{
solve();
}
B
带权值并查集,merge的时候维护并查集顶端的数值,find的时候更新merge时更改的值。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6 + 10;
int pre[N], up[N], s[N], n;
int find(int x)
{
if (x == pre[x])
{
return x;
}
else
{
int k = pre[x];
pre[x] = find(pre[x]);
up[x] += up[k];
s[x] = s[pre[x]];
return pre[x];
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
pre[i] = i;
s[i] = 1;
up[i] = 0;
}
for (int i = 1; i <= n; i++)
{
char a;
cin >> a;
if (a == 'M')
{
int l, r;
cin >> l >> r;
l = find(l), r = find(r);
pre[r] = l;
up[r] = s[l];
up[l] = 0;
s[l] += s[r];
}
else
{
int x;
cin >> x;
int y = find(x);
cout << s[y] - up[x] - 1 << endl;
}
}
}
int main()
{
solve();
}
C
这题运用了lca虽然给的是一张图,但是题目所给条件说明了图一定联通,因此在不考虑重复的边和删去的边的情况下,也一定是连通的,利用这一点建树,dfs预处理lca,如果插入边后成环,就根据插入边两端点的lca路径,经过点权值设置为0,查询两端点有多少航桥时,利用两端点的求lca过程,计数有多少边权值为1即可。
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int N = 4e4 + 10;
vector<int> edge[N];
int fa[N], vis[N], dep[N], pre[N], idx, v[N];
int n, m;
pair<int, int> e[400010];
map<pair<int, int>, int> mp;
struct node
{
int op, a, b;
}ask[N];
int find(int x)
{
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void dfs(int u, int far, int d)
{
fa[u] = far;
dep[u] = d;
for (auto x : edge[u])
{
if (x == far) continue;
dfs(x, u, d + 1);
}
}
void change(int a, int b)
{
if(dep[a] < dep[b]) swap(a, b);
while (dep[a] != dep[b])
{
v[a] = 0;
a = fa[a];
}
while (a != b)
{
v[a] = 0, v[b] = 0;
a = fa[a], b = fa[b];
}
}
int query(int a, int b)
{
int res = 0;
if (dep[a] < dep[b]) swap(a, b);
while (dep[a] != dep[b])
{
res += v[a];
a = fa[a];
}
while (a != b)
{
res += v[a] + v[b];
a = fa[a], b = fa[b];
}
//cout << res << endl;
return res;
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
pre[i] = i;
v[i] = 1;
}
for (int i = 1; i <= m; i++)
cin >> e[i].first >> e[i].second;
int op;
while (cin >> op)
{
if (op == -1)break;
int a, b;
cin >> a >> b;
if (op == 0) mp[{a, b}] = 1;
idx++;
ask[idx].op = op, ask[idx].a = a, ask[idx].b = b;
}
//puts("111");
for (int i = 1; i <= m; i++)
{
int a = e[i].first, b = e[i].second;
if (mp[{a, b}] == 1) continue;
int fa = find(a), fb = find(b);
if (fa != fb)
{
pre[fa] = fb;
edge[a].push_back(b);
edge[b].push_back(a);
mp[{a, b}] = 1;
}
}
//puts("dfs");
dfs(1, -1, 0);
fa[1] = 1;
for (int i = 1; i <= m; i++)
{
int a = e[i].first, b = e[i].second;
if (mp[{a, b}] == 1) continue;
change(a, b);
}
vector<int> ans;
for (int i = idx; i; i--)
{
if (ask[i].op == 1)
ans.push_back(query(ask[i].a, ask[i].b));
else change(ask[i].a, ask[i].b);
}
reverse(ans.begin(), ans.end());
for (auto x : ans) cout << x << endl;
}
int main()
{
solve();
}
I
要想知道这个房间是否有人,一定需要知道到房间i为止的前缀和以及到房间i-1为止的前缀和,题目要求所有房间的值。
而这些不同房间的前缀和也可以进行合并与差,得到一个新的,这题本质就是求n个房间,0到n的前缀和点的最小生成树。
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 3e6 + 10;
int pre[N];
struct node
{
int x, y, val;
}e[N];
int cmp(node a, node b)
{
return a.val < b.val;
}
int find(int x)
{
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void solve()
{
int n, cnt = 0, ans = 0, sum = 0;
cin >> n;
for (int i = 0; i <= n; i++)pre[i] = i;
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
e[++cnt].x = i - 1;
e[cnt].y = j;
cin >> e[cnt].val;
}
}
sort(e + 1, e + 1 + cnt, cmp);
for (int i = 1; i <= cnt; i++)
{
int x = e[i].x, y = e[i].y;
x = find(x), y = find(y);
if (x != y)
{
ans += e[i].val;
pre[x] = y;
//sum++;
}
}
cout << ans << endl;
}
signed main()
{
solve();
}
K
这题有一点二分的思想,每次把图形分割成两部分,直到最小成分即i-i+1的组合,对于最小组成,我们有三种操作,删去最左边的点,删去最右边的点,左右都删。我们从最大图形二分递归到最小组成,在回溯过程中,进行状态转移,
第一个操作从左边开始删的状态: 左儿子和右儿子均从左边删顶点, 或者 左儿子左右删顶点+右儿子从左边删顶点
第二个操作从右边开始删的状态: 左儿子和右儿子均从右边删顶点, 或者 右儿子左右删顶点+左儿子从右边删顶点
第三个操作左右两边同时删:左儿子和右儿子均从左右删顶点, 或者 左儿子从左边删顶点+右儿子从右边删顶点
建边的主要目的是为了维护二分过程,所以只连j比i大的边为i->j,。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2e5 + 10;
struct node
{
int l, r, lr;
}dp[N];
vector<int> to[N];
int idx, num[N], ls[N], rs[N];
int dfs(int l, int r)
{
//cout << l << " " << r << endl;
int p = ++idx;
if (l == r - 1)
dp[p].l = 1, dp[p].r = 1, dp[p].lr = 2;
else
{
int mid = to[l][++num[l]];
ls[p] = dfs(l, mid);
rs[p] = dfs(mid, r);
dp[p].l = min(dp[ls[p]].l + dp[rs[p]].l, dp[ls[p]].lr + dp[rs[p]].l - 1);
dp[p].r = min(dp[ls[p]].r + dp[rs[p]].r, dp[ls[p]].r + dp[rs[p]].lr - 1);
dp[p].lr = min(dp[ls[p]].l + dp[rs[p]].r, dp[ls[p]].lr + dp[rs[p]].lr - 1);
}
return p;
}
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n * 2 - 3; i++)
{
int a, b;
cin >> a >> b;
if (a > b) swap(a, b);
to[a].push_back(b);
}
for (int i = 1; i <= n; i++)
sort(to[i].begin(), to[i].end(), greater<int>());
dfs(1, n);
int ans = min(dp[1].l,dp[1].r);
ans = min(ans, dp[1].lr);
cout << ans << endl;
}
signed main()
{
solve();
}