文章目录
1006题
Maex
题目大意
给出一棵树,以 1 1 1 为根节点,树上的每个节点都有自己的点权 a i a_i ai,以及一个价值 b i b_i bi , b i b_i bi 是 i i i 子树中 a a a 值集合的 M E X MEX MEX (集合中未出现的最小非负整数), a i a_i ai 的值可以为任意的自然数,题目保证 a i a_i ai 的值不同。求 b i b_i bi 加和的最大值。
思路
由题意可以看出,这是一道典型的树形dp问题,通过对样例的模拟可知,只有从根节点到某个叶子节点路径上的点才会对答案有所贡献,并且贡献值为该子树的节点个数。所以我们应当找到一条最长的路径来使得
b
i
b_i
bi 的贡献最大。
所以我们可以用
v
a
l
[
i
]
val[i]
val[i] 表示以
i
i
i 叶子节点的子树中得到的
b
i
b_i
bi 加和最大值,
c
n
t
[
i
]
cnt[i]
cnt[i] 记录该节点的节点个数,最后
d
f
s
dfs
dfs 即可得到结果。
代码
#include<iostream>
#include<vector>
#define int long long
using namespace std;
const int N = 5e5+10;
vector<int> v[N];
int cnt[N];//节点个数
int val[N];//价值
void dfs(int now,int root){
int maxm = 0;
for(auto x:v[now]){//遍历子节点,并找到子节点中的最大值
if(x == root)continue;
dfs(x,now);
cnt[now] += cnt[x] + 1;
maxm = max(maxm,val[x]);
}
val[now] = maxm + cnt[now] + 1;
}
void solve(){
int n;
cin>>n;
for(int i = 1;i <= n;i ++){
v[i].clear();
val[i] = 0;
cnt[i] = 0;
}
for(int i = 1;i <= n-1;i ++){
int x,y;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1,0);
cout<<val[1]<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin>>t;
while(t--)solve();
system("pause");
return 0;
}
1007题
Shinobu loves trip
题目大意
给定常数 a a a 和 p p p,你会进行 n n n 次旅行,每次旅行的起点是 s s s,第一天的位置在 s s s, d d d 天之后的位置是 ( s ⋅ a d ) m o d P (s\cdot a^d)\ mod\ P (s⋅ad) mod P。有 q q q 个询问,每次询问一个位置 x x x,问你一共经过多少次 x x x。
思路
判断 x x x 是否会被经过,只需要判断是否存在 0 ≤ k ≤ d 0\le k\le d 0≤k≤d 使得 ( s ⋅ a k ) m o d p = x (s\cdot a^k)\ mod\ p=x (s⋅ak) mod p=x,因为 p p p 是质数,所以知道 x x x 和 s s s 就能求出 a k a^k ak。
我们可以先预处理出 a 0 , a 1 , ⋯ , a d a^0,a^1,\cdots,a^d a0,a1,⋯,ad 保存在 map 中,然后判断算出来的 a k a^k ak 是否在 map 中,如果不在,则此次旅行一定不经过 x x x;否则,可以求出 k k k,判断 k k k 是否 ≤ d \le d ≤d 即可。
代码
int n, a, p, q, s[N], d[N];
LL inv[N];
LL powmod(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1) (ans *= a) %= p;
(a *= a) %= p;
b >>= 1;
}
return ans;
}
void solve() {
scanf("%d%d%d%d", &p, &a, &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &s[i], &d[i]);
if (s[i]) inv[i] = powmod(s[i], p - 2);
}
std::unordered_map<int, int> mp;
int x = 1, t = 0;
while (t <= 200000) {
if (mp.count(x)) break;
mp[x] = t;
x = 1LL * x * a % p;
t++;
}
while (q--) {
int x;
scanf("%d", &x);
int ans = 0;
if (!x) {
for (int i = 1; i <= n; i++) ans += (s[i] == 0);
} else {
for (int i = 1; i <= n; i++)
if (s[i]) {
int k = x * inv[i] % p;
if (mp.count(k) && mp[k] <= d[i]) ans++;
}
}
printf("%d\n", ans);
}
}
1010题
Planar graph
题目大意:
给定一个平面图(即图中任意两条边之间不相交),该平面图有 n n n 个顶点, m m m 条边,每条边均有一个编号,编号为 1 , 2 , … , m 1,2,\dots,m 1,2,…,m 。设给定平面图中的边围成的平面区域有 k k k 个,分别标记为 1 , 2 , … , k 1,2,\dots,k 1,2,…,k 。现在,我们通过给一些边打上通道(相当于将这些边删去),使得平面区域两两之间可以相互到达。求最少需要删除多少边,输出需要删除的边的数量,同时输出所有可行方案中字典序最小的方案。
分析:
通过对图的分析,我们知道,一条边是否需要要被打通(删除),取决于该边是否在一个环上,即取决于该边是否对围成一片新的平面区域有贡献。由于我们要输出字典序最小的方案,所以,在所有的边当中,我们取边的编号最小的那个即可。
判定是否成环有多种方式,这里我们使用并查集来判定是否成环。参考例题——格子游戏。具体实现:我们先将所有的边存起来,初始时,我们将每个点作为一个单独的集合,即令 p [ i ] = i p[i] = i p[i]=i 。之后,我们从编号最大的边开始,从大到小遍历所有的边。 每遍历到一条边,若两个点不在同一个集合,我们便将两个点所在的集合合并;若在同一个集合,则说明这条边的加入刚好使得图中形成了一个新的环,这个时候,我们便将该边的编号加入到答案中。由于是倒序遍历,每次形成一个新的环时,加入的边必定是环上编号最小的边。整体代码实现还算简单。
参考代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
typedef long long ll;
struct Edge {
int x, y;
}edge[M];
int p[N];
int find(int x) {
if(p[x] == x) return x;
else return p[x] = find(p[x]);
}
void solve() {
int n, m; scanf("%d %d", &n, &m);
for(int i = 1; i <= m; i ++ ) {
scanf("%d %d", &edge[i].x, &edge[i].y);
}
for(int i = 1; i <= n; i ++ ) p[i] = i;
vector<int> ans;
for(int i = m; i > 0; i -- ) {
int a = edge[i].x, b = edge[i].y;
int x = find(a), y = find(b);
if(x == y) ans.push_back(i);
else p[y] = x;
}
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
for(int i = 0; i < ans.size(); i ++ ) {
printf("%d ", ans[i]);
}
puts("");
}
int main() {
int t; scanf("%d", &t);
while(t -- ) {
solve();
}
return 0;
}
1012题
Loop
题目大意
每次操作可以将数组中的一个数向后移 n n n 位,问 k k k 次操作后字典序的最大值
分析
数据范围较大,故基本采用 O ( n ) O(n) O(n) 的做法。我们可以先把要取的数取出来,然后再一起插到后面。我们可以发现,从前往后比较两个数,当前数比后数小时,就可以把前数取出,然后再比较后数和前数的前数,直到前数比后数大为止,然后再往后比较。可以发现我们是在进行一个维护单调递减栈的操作,当新插入的数比栈顶大时,就将栈顶弹出,直到插入的数比栈顶小,或者是已经进行了 k k k 次操作为止。接下来就可以将弹出的数插入到后面,首先可以对弹出的数降序排列,然后依然是从前往后依次比较,当要插入的数比当前数大的时候,就将数插入到这个数之前,直到插入的数小于当前数为止。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N=3e5+1;
int T,n,k;
int a[N],b[N],c[N],s[N];
bool cmp(int a,int b){return a>b;}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
for(register int i=1;i<=n;i++) scanf("%d",&a[i]);
int top=0,now=0;
//维护单调栈
for(register int i=1;i<=n;i++)
{
if(now>=k) s[++top]=a[i];//已弹出k个数
else
{
while(top&&now<=k&&s[top]<a[i]) b[++now]=s[top--];
s[++top]=a[i];
}
}
//将弹出数组插入
sort(b+1,b+now+1,cmp);
int nows=1,nowb=1,nowc=0;
for(register int i=1;i<=top;i++)
{
if(nowb>now) c[++nowc]=s[i];
else
{
while(b[nowb]>s[i]) c[++nowc]=b[nowb++];
c[++nowc]=s[i];
}
}
while(nowb<=now) c[++nowc]=b[nowb++];
printf("%d",c[1]);
for(register int i=2;i<=n;i++) printf(" %d",c[i]);
printf("\n");
}
return 0;
}