设 f [ i ] [ p ] f[i][p] f[i][p]表示当前已经完成了前i个任务,当前正在 a [ i ] a[i] a[i]上,传送门的位置在 p p p。
•可以证明,只需要3种转移,就可以覆盖所有情况:
•1.直接从a[i]走到a[i+1]
•2.枚举走到a[i+1]之后,传送门的位置变为了哪个节点,设这个节点是q。第二种转移是从a[i]走到 q,在q设置传送门,从q传送到 p p p,再从 p p p走到 a [ i + 1 ] a[i + 1] a[i+1]
•3.第三种转移是从 a [ i ] a[i] a[i]传送到 p p p,从 p p p走到 q q q,在 q q q设置传送门,最后从 q q q走到 a [ i + 1 ] a[i + 1] a[i+1]
•复杂度为 O ( k n 2 ) O(kn^2) O(kn2)
初始状态的最短路用Floyd最短路求出。
#include
using namespace std;
typedef long long LL;
const int MAXN = 305;
const LL INF = 2e18;
inline void io()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
}
LL f[MAXN][MAXN];
LL d[MAXN][MAXN];
int a[MAXN * 2];
int main()
{
int n, m, k;
scanf("%d %d %d", &n, &m, &k);
memset(d, 0x16, sizeof(d));
memset(f, 0x16, sizeof(f));
// cout << "d = " << d[1][2] << '\n';
for (int i = 1; i <= n; i++)
d[i][i] = 0;
for (int i = 1; i <= m; i++)
{
int u, v;
LL w;
scanf("%d %d %lld", &u, &v, &w);
d[u][v] = d[v][u] = min(d[u][v], w);
}
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
// if (i == j || k == i || k == j)
// continue;
d[i][j] = d[j][i] = min(d[i][j], d[i][k] + d[k][j]);
}
}
// for (int i = 1; i <= n; i++)
// {
// for (int j = 1; j <= n; j++)
// {
// if (d[i][j] != INF)
// cout << "i = " << i << " j = " << j << " dpij = " << d[i][j] << "\n";
// }
// }
for (int i = 1; i <= n; i++)
f[1][i] = d[1][i];
a[1] = 1;
for (int i = 1; i <= k; i++)
{
scanf("%d %d", &a[i * 2], &a[i * 2 + 1]);
}
for (int i = 2; i <= 2 * k + 1; i++)
{
for (int p = 1; p <= n; p++)
{
f[i][p] = min(f[i][p], f[i - 1][p] + d[a[i - 1]][a[i]]);
for (int q = 1; q <= n; q++)
{
f[i][q] = min(f[i][q], f[i - 1][p] + d[p][q] + d[q][a[i]]);
f[i][q] = min(f[i][q], f[i - 1][p] + d[a[i - 1]][q] + d[p][a[i]]);
}
}
}
LL ans = f[2 * k + 1][1];
for (int i = 1; i <= n; i++)
{
ans = min(ans, f[2 * k + 1][i]);
}
printf("%lld", ans);
// int t;
// cin >> t;
}
本题需要线段树和主席树一起来求解,对于区间的与操作,容易想到采用线段树来求解。
容易想到,对于每一个 A i A_i Ai作为右端点,向左的所有区间里,不同的值的个数为 x x x(其中, x = A i x = A_i x=Ai的二进制中,1的个数)。
主席树的每一个根节点的 r t [ i ] rt[i] rt[i],代表以序列中的第 i i i个点为右端点,向左的每一个区间里得不同得数的个数。
每加入一个 A i A_i Ai,就在主席树中新建一个根节点,将前一个出现值为 A i A_i Ai的位置减一,当前位置加一,枚举固定该节点为右端点,向左的每一次减少的位置,在那个位置加一,前一个这个值的位置减一。
这样在询问时,只要在R为根节点的主席树中询问一的个数的和,即为不同的个数的和
#include
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 5;
inline void io()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
}
namespace Chairman
{
const int N = 1e5 + 5;
int ls[N * 300], rs[N * 300], rt[N], sum[N * 300], tot;
void update(int l, int r, int root, int last, int p, int v)
{
ls[root] = ls[last];
rs[root] = rs[last];
sum[root] = sum[last] + v;
if (l == r)
return;
int mid = l + r >> 1;
if (mid >= p)
update(l, mid, ls[root] = ++tot, ls[last], p, v);
else
update(mid + 1, r, rs[root] = ++tot, rs[last], p, v);
}
int query(int l, int r, int root, int ql, int qr)
{
if (l >= ql && r <= qr)
return sum[root];
int mid = l + r >> 1;
int ans = 0;
if (mid >= ql)
ans = query(l, mid, ls[root], ql, qr);
if (mid < qr)
ans += query(mid + 1, r, rs[root], ql, qr);
return ans;
}
}; // namespace Chairman
namespace Segtree
{
const int MAXN = 1e5 + 5;
struct node
{
int l, r;
LL sum, lazy;
} tree[MAXN << 2];
LL a[MAXN]; //you can put the num to this first
inline void build(int i, int l, int r)
{
tree[i].l = l;
tree[i].r = r;
tree[i].lazy = 0;
if (l == r)
{
tree[i].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(i * 2, l, mid);
build(i * 2 + 1, mid + 1, r);
tree[i].sum = tree[i * 2].sum & tree[i * 2 + 1].sum;
}
inline LL search(int i, int l, int r)
{
if (tree[i].l >= l && tree[i].r <= r)
{
return tree[i].sum;
}
if (tree[i].r < l || tree[i].l > r)
return 0;
LL ans = (1 << 30) - 1;
if (tree[i * 2].r >= l)
ans &= search(i * 2, l, r);
if (tree[i * 2 + 1].l <= r)
ans &= search(i * 2 + 1, l, r);
return ans;
}
} // namespace Segtree
int a[MAXN];
map<int, int> pre;
int main()
{
// Chairman::tot = 0;
// Chairman::rt[0] = 0;
// Chairman::update(1, 10, Chairman::rt[1] = ++Chairman::tot, Chairman::rt[0], 3, 1);
// Chairman::update(1, 10, Chairman::rt[2] = ++Chairman::tot, Chairman::rt[1], 1, -1);
// Chairman::update(1, 10, Chairman::rt[3] = ++Chairman::tot, Chairman::rt[2], 6, 2);
// cout << Chairman::query(1, 10, Chairman::rt[1],1,3) << '\n';
// cout << Chairman::query(1, 10, Chairman::rt[3],3,6) << '\n';
// cout << Chairman::query(1, 10, Chairman::rt[3],1,6) << '\n';
int n;
// cin >> n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
// cin >> a[i];
scanf("%d", &a[i]);
Segtree::a[i] = a[i];
}
Segtree::build(1, 1, n);
Chairman::tot = 0;
Chairman::rt[0] = 0;
for (int i = 1; i <= n; i++)
{
if (pre.find(a[i]) != pre.end())
{
// int nowrt = Chairman::tot;
Chairman::update(1, n, Chairman::rt[i] = ++Chairman::tot, Chairman::rt[i - 1], pre[a[i]], -1);
int now = ++Chairman::tot;
Chairman::update(1, n, now, Chairman::rt[i], i, 1);
Chairman::rt[i] = now;
}
else
Chairman::update(1, n, Chairman::rt[i] = ++Chairman::tot, Chairman::rt[i - 1], i, 1);
pre[a[i]] = i;
int v = a[i], p = i;
for (int j = 30; j; j--)
{
int l = 1, r = p - 1;
int ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (Segtree::search(1, mid, i) < v)
l = mid + 1, ans = mid;
else
{
r = mid - 1;
}
}
if (ans == 0)
break;
v = Segtree::search(1, ans, i);
p = ans;
int now;
if (pre.find(v) != pre.end())
{
now = ++Chairman::tot;
Chairman::update(1, n, now, Chairman::rt[i], pre[v], -1);
Chairman::rt[i] = now;
}
now = ++Chairman::tot;
Chairman::update(1, n, now, Chairman::rt[i], p, 1);
Chairman::rt[i] = now;
pre[v] = p;
}
}
int Q;
scanf("%d", &Q);
int lastans = 0;
while (Q--)
{
int L, R;
scanf("%d %d", &L, &R);
L = (L ^ lastans) % n + 1;
R = (R ^ lastans) % n + 1;
if (L > R)
swap(L, R);
lastans = Chairman::query(1, n, Chairman::rt[R], L, R);
printf("%d\n", lastans);
}
// int t;
// cin >> t;
}