2020牛客暑期多校训练营(第五场)

题目: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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值