题目:A.Portal
分析:
理解:
可以将k个点拆成2 *k个点,每个点看做一个单独的任务。每完成一个任务的时候只需要保留一个Portal,因为下一次转移的时候到达要转移的点再建立新的Portal也不迟。dp[i][j]代表完成第i个任务的时候,保留第j个Portal的最短距离。我们可以把初始状态定义为第0个任务,且Portal 1已经建立。
状态转移:
对于第i个任务, 建立了第j个protal,则他的路线一定是 i - 1 -->j -->i
我们再枚举i - 1个任务完成时的protal p
前段路线=min(i - 1 - > p - > j, i - 1 - > j)
后段路线=min(j - >i, j ->p - > i)
注意:
当j=p的时候,前段路线为0,后段路线还要再跟(i - 1 -> i)取个min
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
const ll mod = 1e9 + 7;
const int maxn = 310;
const ll inf = 1e18;
ll loc[maxn << 1], dp[maxn << 1][maxn];
ll dis[maxn][maxn];
int main()
{
int n, m, k; cin >> n >> m >> k;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
dis[i][j] = i == j ? 0 : inf;
for(int i = 1; i <= m; i++)
{
ll a, b, w; cin >> a >> b >> w;
dis[a][b] = dis[b][a] = min(w, dis[a][b]);
}
for(int p = 1; p <= n; p++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
dis[i][j] = min(dis[i][j], dis[i][p] + dis[p][j]);
for(int i = 1; i <= k; i++)
{
int a, b; cin >> a >> b;
loc[i * 2 - 1] = a, loc[i * 2] = b;
}
for(int i = 0; i <= 2 * k; i++)//注意:这里0也要初始化
for(int j = 1; j <= n; j++)
dp[i][j] = inf;
loc[0] = 1;
dp[0][1] = 0;
for(int i = 1; i <= 2 * k; i++)
for(int j = 1; j <= n; j++)
for(int p = 1; p <= n; p++)
{
dp[i][j] = min(dp[i][j], dp[i - 1][j] + dis[loc[i - 1]][loc[i]]);
ll dis1 = min(dis[loc[i - 1]][j], dis[p][j]);
ll dis2 = min(dis[j][loc[i]], dis[p][loc[i]]);
dp[i][j] = min(dp[i][j], dp[i - 1][p] + dis1 + dis2);
}
ll minn = inf;
for(int i = 1; i <= n; i++)
minn = min(minn, dp[k * 2][i]);
cout << minn << endl;
}
题目:B.Graph
分析:
转化为最小异或生成树来做,原因是点与点之间的距离是确定的(通过构成环的时候异或值为0得出),那么我们可以设每个点的权值为根节点到当前点的异或值。然后套dfs分治求最小异或生成树的板子。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
struct edge
{
ll v, val, nex;
edge(ll v = 0, ll val = 0, ll nex = 0) : v(v), val(val), nex(nex){}
}e[maxn << 1];
ll trie[maxn * 40][2], a[maxn], l[maxn * 40], r[maxn * 40];
int num;
int head[maxn], tot;
void init()
{
mem(head, -1), tot = 0;
}
void addedge(ll u, ll v, ll val)
{
e[tot] = edge(v, val, head[u]);
head[u] = tot++;
}
void dfs(int u, int fa, ll val)
{
a[u] = val;
for(int i = head[u]; ~i; i = e[i].nex)
{
int v = e[i].v;
if(v == fa) continue;
dfs(v, u, val ^ e[i].val);
}
}
void insert(ll val, int pos)
{
int now = 0;
for(int i = 30; i >= 0; i--)
{
int x = (val >> i) & 1;
if(!trie[now][x])
{
trie[now][x] = ++num;
now = trie[now][x];
l[now] = r[now] = pos;
}
else
{
now = trie[now][x];
l[now] = min(l[now], (ll)pos), r[now] = max(r[now], (ll)pos);
}
}
}
ll query(ll val, int now, int bit)
{
if(!bit || l[now] == r[now]) return a[l[now]];
int x = (val >> bit - 1) & 1;
if(trie[now][x]) return query(val, trie[now][x], bit - 1);
else if(trie[now][x ^ 1]) return query(val, trie[now][x ^ 1], bit - 1);
else return 0;
}
ll dfs(int now, int bit)
{
if(!bit) return 0;
ll res = 0;
int ls = trie[now][0], rs = trie[now][1];
if(trie[now][0] && trie[now][1])
{
ll ans = 2e18;
for(int i = l[ls]; i <= r[ls]; i++)
ans = min(ans, a[i] ^ query(a[i], rs, bit - 1));
res += ans;
}
if(ls) res += dfs(ls, bit - 1);
if(rs) res += dfs(rs, bit - 1);
return res;
}
int main()
{
int n; cin >> n;
init();
for(int i = 1; i < n; i++)
{
ll u, v, w; cin >> u >> v >> w; u++, v++;
addedge(u, v, w), addedge(v, u, w);
}
dfs(1, -1, 0);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; i++) insert(a[i], i);
cout << dfs(0, 31) << endl;
}
题目:D.Drop Voicing
给你一个n的排列,定义两个操作:
操作1:将倒数第二个元素插到第一个元素前面
操作2:将第一个元素插到最后一个元素后面
一段连续的操作1当做1次操作,操作2随意使用,不计次数。
问至少经过多少次操作(多少段连续的操作1)使得序列为上升的
分析:
将两端首尾相接,设len为环上的LIS, 答案就是n-len。
因为对于不在LIS上的元素必定可以通过多次操作2将其移动到最后一个位置,然后再经过多次操作1,使其加入原来的LIS并构成新的更长的LIS。所以只需求n次LIS即可,复杂度
n
2
l
o
g
n
n^2logn
n2logn
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
const ll mod = 1e9 + 7;
const int maxn = 2e6;
const int inf = 0x3f3f3f3f;
vector<int> a;
int dp[maxn];
int main()
{
int n; cin >> n;
for(int i = 0; i < n; i++)
{
int x; cin >> x; a.push_back(x);
}
int maxx = 0;
for(int k = 0; k < n; k++)
{
int tmp = a[0];
int len = 0;
a.erase(a.begin()), a.push_back(tmp);
dp[++len] = a[0];
for(int i = 1; i < n; i++)
{
int pos = lower_bound(dp + 1, dp + 1 + len, a[i]) - dp;
if(pos == 1 + len) dp[++len] = a[i];
else dp[pos] = a[i];
}
maxx = max(maxx, len);
}
cout << n - maxx << endl;
}
题目:H.Interval
求给定区间内,所有子段连续与值的种数
分析:
跟主席树求区间内数的种数很相似。主席树模板题是同一个数只保存最右边出现的位置,而这个题是同一段连续&,只保存左边界最右边出现的位置。
做法是:枚举右端点,然后求出以该点为右端点的所有的&值,相同的&值取靠右的位置(可以保留上一个右端点的所有&值,将当前的右端点跟其相与(包括跟右端点自己))。然后将得到的值更新主席树即可。
注意:
1、不同的右端点产生的的连续&值如果相同,则其&值保存的位置一定是递增的
2、每个右端点会update多次,所以主席树要提前继承上一个root。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
struct node
{
int l, r, sum;
}t[maxn * 400];
map<int, int> his, pre;
int root[maxn * 400];
int tot;
void update(int pre, int &now, int pos, int val, int l, int r)
{
now = ++tot;
t[now] = t[pre]; t[now].sum += val;
if(l == r) return;
int mid = l + r >> 1;
if(pos <= mid) update(t[pre].l, t[now].l, pos, val, l, mid);
else update(t[pre].r, t[now].r, pos, val, mid + 1, r);
}
int query(int tar, int posl, int l, int r)
{
if(l == r) return t[tar].sum;
int mid = l + r >> 1;
if(posl <= mid) return t[t[tar].r].sum + query(t[tar].l, posl, l, mid);
else return query(t[tar].r, posl, mid + 1, r);
}
int main()
{
int n; cin >> n;
for(int i = 1; i <= n; i++)
{
int x; cin >> x;
map<int, int> now;
pre[x] = i;
for(auto it = pre.begin(); it != pre.end(); it++)
{
int fi = it->first, se = it->second;
now[fi & x] = max(now[fi & x], se);
}
root[i] = root[i - 1];
for(auto it = now.begin(); it != now.end(); it++)
{
int fi = it->first, se = it->second;
if(his.count(fi))
update(root[i], root[i], his[fi], -1, 1, n);
his[fi] = se, update(root[i], root[i], his[fi], 1, 1, n);
}
pre = now;
}
int q; cin >> q;
int last = 0;
while(q--)
{
int l, r; cin >> l >> r;
int k1 = (l ^ last) % n + 1, k2 = (r ^ last) % n + 1;
l = min(k1, k2), r = max(k1, k2);
cout << (last = query(root[r], l, 1, n)) << endl;
}
}
附上主席树区间数的个数模板代码:
题目链接
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
const ll mod = 1e9 + 7;
const int maxn = 5e5 + 10;
const int inf = 0x3f3f3f3f;
struct node
{
int l, r, sum;
node(int l = 0, int r = 0, int sum = 0) : l(l), r(r), sum(sum){};
}t[maxn * 50];
int root[maxn];
int tot;
void build(int &tar, int l, int r)
{
tar = ++tot;
t[tar].sum = 0;
if(l == r) return;
int mid = l + r >> 1;
build(t[tar].l, l, mid), build(t[tar].r, mid + 1, r);
}
void update(int pre, int &now, int l, int r, int pos, int val)
{
now = ++tot;
t[now] = t[pre];
t[now].sum += val;
if(l == r) return;
int mid = l + r >> 1;
if(pos <= mid) update(t[pre].l, t[now].l, l, mid, pos, val);
else update(t[pre].r, t[now].r, mid + 1, r, pos, val);
}
int query(int posl, int tar, int l, int r)
{
if(l == r) return t[tar].sum;
int mid = l + r >> 1;
if(posl <= mid) return t[t[tar].r].sum + query(posl, t[tar].l, l, mid);
else return query(posl, t[tar].r, mid + 1, r);
}
map<int, int> vis;
int main()
{
close();
int n, q; scanf("%d%d", &n, &q);
build(root[0], 1, n);
for(int i = 1; i <= n; i++)
{
int x; scanf("%d", &x);
if(!vis[x]) vis[x] = i, update(root[i - 1], root[i], 1, n, i, 1);
else update(root[i - 1], root[i], 1, n, vis[x], -1), vis[x] = i, update(root[i], root[i], 1, n, i, 1);
}
while(q--)
{
int l, r; scanf("%d%d", &l, &r);
cout << query(l, root[r], 1, n) << endl;
}
}