Loading

The 2023 ICPC Asia Nanjing Regional Contest (The 2nd Universal Cup. Stage 11: Nanjing)(SDKD 2024 Summer Training Contest G1)



C - Primitive Root

题意

给定p与m(p为质数),已知(g ^ (P - 1)) % P == 1且g <= m。求g的个数。

思路

由(g ^ (P - 1)) % P == 1与异或性质a - b <= a ^ b <= a + b,可以推出g = ((k * p + 1) ^ (p - 1))与p * (k - 1) + 2 <= g <= p * (k + 1)。又因为g <= m,则当p * (k + 1) <= m,一定合法;p * (k - 1) + 2 > m一定不合法;二者之间单独判断。
原根(虽然没什么用)https://zhuanlan.zhihu.com/p/591377528

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;

#define int long long

const int mxn = 1e6 + 5;

void solve()
{
    int m, p, ans = 0;
    scanf("%lld%lld", &p, &m);
    // (k < m \ p - 1)一定满足(加上0共n / p个),(k > (m - 2) / p + 1)一定不满足
    int minn = m / p, maxn = (m - 2) / p + 1;
    if ((m - 2) % p) // 考虑取整
    {
        maxn++;
    }
    for (int k = minn; k <= maxn; k++) // 二者之间单独判断,数据量很小
    {
        int g = ((k * p + 1) ^ (p - 1));
        if (g <= m)
        {
            ans++;
        }
    }
    printf("%lld\n", m / p + ans); 
}

signed main()
{
    int T = 1;
    scanf("%lld", &T);
    while (T--)
    {
        solve();
    }

    return 0;
}

F - Equivalent Rewriting

题意

长度为m的序列全初始化为0,n次操作,第i次操作把给出的k个位置的值换成i。问重新排列操作能否得到原来序列。

思路

对于每个位置,按序进行操作\({a_1, a_2,···, a_k}\),则最后的值只能是\(a_k\)。考虑各操作之间有先后的关系,可以把每次操作看作点,所建图的任意拓扑序即为答案。

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int mxn = 1e6 + 5;

int n, m, idx, cnt;
int to[mxn], nxt[mxn], head[mxn], indegree[mxn], ans[mxn];
vector<vector<int>> p;

void init()
{
	fill(head, head + n + 1, 0);
	fill(indegree, indegree + n + 1, 0);
	p.clear();
	p.resize(m + 1);
	idx = cnt = 0;
}

void add(int u, int v)
{
	to[++idx] = v;
	nxt[idx] = head[u];
	head[u] = idx;
}

void toposort()
{
	priority_queue<int> q;
	for (int i = 1; i <= n; i++)
	{
		if (!indegree[i])
		{
			q.push(i);
		}
	}
	while (q.size())
	{
		int u = q.top();
		q.pop();
		ans[++cnt] = u;
		for (int i = head[u]; i; i = nxt[i])
		{
			int v = to[i];
			indegree[v]--;
			if (!indegree[v])
			{
				q.push(v);
			}
		}
	}
}

void solve()
{
	scanf("%lld%lld", &n, &m);
	init();

	for (int i = 1; i <= n; i++)
	{
		int k;
		scanf("%lld", &k);
		for (int j = 1; j <= k; j++)
		{
			int x;
			scanf("%lld", &x);
			p[x].push_back(i);
		}
	}

	for (int i = 1; i <= m; i++)
	{
		int sz = p[i].size();
		for (int j = 0; j < sz - 1; j++)
		{
			add(p[i][j], p[i][sz - 1]);
			indegree[p[i][sz - 1]]++;
		}
	}

	toposort();

	for (int i = 1; i <= n; i++)
	{
		if (ans[i] != i) // 检查跟原操作顺序是否相同
		{
			printf("Yes\n");
			for (int i = 1; i <= n; i++)
			{
				printf("%lld%c", ans[i], (i == n ? '\n' : ' '));
			}
			return;
		}
	}
	printf("No\n");
}

signed main()
{
	int T = 1;
	scanf("%lld", &T);
	while (T--)
	{
		solve();
	}

	return 0;
}

G - Knapsack

题意

n个物品,每个物品有w与v两种属性。现有W元(钱不必花完),能免费拿k个,问v的最大和。

思路

一眼背包,考虑到还能免费拿k个,将物品按照w升序排序,显然存在一个分界点,使得做背包的物品都在某段前缀中,而零元购的物品都在对应的后缀中。预处理后缀中取前k个v最大的,边跑背包边跟新ans。

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;

#define int long long
typedef pair<int, int> pii;

const int mxn = 1e4 + 5;

int dp[mxn];

void solve()
{
    int n, w, k;
    scanf("%lld%lld%lld", &n, &w, &k);
    vector<pii> v(n); // {w, v}
    for (int i = 0; i < n; i++)
    {
        scanf("%lld%lld", &v[i].first, &v[i].second);
    }
    sort(v.begin(), v.end());
    priority_queue<int, vector<int> , greater<int>> heap; // 小根堆维护后缀
    vector<int> pre(n + 1);
    int sum = 0;
    for (int i = n - 1; i >= 0; i--)
    {
        sum += v[i].second;
        heap.push(v[i].second);
        if (heap.size() > k) // 取前k大的
        {
            sum -= heap.top();
            heap.pop();
        }
        pre[i] = sum;
    }
    //01背包
    int ans = -1;
    for (int i = 0; i < n; i++)
    {
        for (int j = w; j >= v[i].first; j--)
        {
            dp[j] = max(dp[j], dp[j - v[i].first] + v[i].second);
        }
        ans = max(ans, dp[w] + pre[i + 1]); // 前i个跑背包,后边的免费拿k个
    }
    printf("%lld", ans);
}

signed main()
{
    int T = 1;
    //scanf("%lld", &T);
    while (T--)
    {
        solve();
    }

    return 0;
}


I - Counter

题意

n次操作,操作分两种:归零或+1;m个条件,每个条件给定a与b,表示第a次操作后值为b。问是否存在所给情况。

思路

按a给操作排序,由于每次只能+1,故\(b \le a\);对于相邻两次操作,相差\(a_i - a_j\)次操作,可以全部加\(b_j - b_i = a_j - a_i\),也可以几次归零几次加,就可以得出范围:\(b_j \ge 0\)(全归零)且\(b_j <= s2 - a1 - 1\)(归零一次),但题目保证了\(b \ge 0\),故只用看后者。

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;

#define int long long
typedef pair<int, int> pii;

void solve()
{
    int n, m;
    scanf("%lld%lld", &n, &m);
    vector<pii> v(m);
    for (int i = 0; i < m; i++)
    {
        scanf("%lld%lld", &v[i].first, &v[i].second);
    }
    if (m == 1) // 特判
    {
        printf(v[0].second > v[0].first ? "No\n" : "Yes\n");
        return;
    }
    sort(v.begin(), v.end());
    for (int i = 0; i < m - 1; i++)
    {
        int a1 = v[i].first, b1 = v[i].second;
        int a2 = v[i + 1].first, b2 = v[i + 1].second;
        if (b1 > a1 || b2 > a2)
        {
            printf("No\n");
            return;
        }
        if (b2 - b1 == a2 - a1 || b2 <= a2 - a1 - 1) // 全加或归零一次后全加
        {
            continue;
        }
        else
        {
            printf("No\n");
            return;
        }
    }
    printf("Yes\n");
}

signed main()
{
    int T = 1;
    scanf("%lld", &T);
    while (T--)
    {
        solve();
    }

    return 0;
}


比赛链接 https://vjudge.net/contest/649185#overview

posted @ 2024-09-03 20:25  _SeiI  阅读(147)  评论(0)    收藏  举报