2022 HDU多校6
Maex
Problem
给定一个大小为\(n\)的树,每个节点有一个权值\(a_i\),并且保证不存在两个点的点权相同,定义\(p_u=\text{mex}\{x|v\in subtree\left(u\right),x=a_i\}\)。现在你可以给这些点赋予点权,使得\(\sum_{i=1}^np_i\)最大,请求出这个值。
\(1\le n\le 5\times 10^5\)
Solve
hit1
:保证不存在两个点的点权相同
根据这个,考虑一个节点\(u\)的\(p_u\)不为\(0\),当且仅当它的子树里面出现过点权为\(0\)的节点,而这个点权为\(0\)的节点当且仅有一个,然后一直往这个子树里面找,发现\(p\)不为\(0\)的点一定形成一条链,并且这条链上的节点的\(p\)就是这个节点子树的大小,因为考虑这个子树中不存在\(0\)权值点的那些链,发现他们的贡献一定是\(0\),那我们直接按照顺序赋值可以最优,所以直接\(dfs\)一直往下搜索找到最大值即可
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n;
cin>>n;
vector<int>sz(n+1);
vector<vector<int>>E(n+1);
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
auto dfs1=[&](auto self,int u,int fa)->void{
sz[u]=1;
for(auto v:E[u]){
if(v==fa) continue;
self(self,v,u);
sz[u]+=sz[v];
}
sort(E[u].begin(),E[u].end(),[&](int i,int j){return sz[i]>sz[j];});
};
dfs1(dfs1,1,1);
ll ans=0;
auto dfs2=[&](auto self,int u,int fa,ll w)->void{
ans=max(ans,w);
for(auto v:E[u]){
if(v==fa) continue;
self(self,v,u,w+sz[v]);
}
};
dfs2(dfs2,1,1,n);
cout<<ans<<'\n';
}
}
Shinobu loves trip(数论、卡常)
Problem
有\(n\)个任务,每个任务可以用\((s_i,d_i)\)来表示,现在有\(q\)个询问,每次询问给定一个数\(x\),问有多少个任务满足\(\exist j,0\le j\le d_i\),\(s_ia^j\equiv x\mod{P}\),其中\(a,P\)题目给定。
\(1\le n,q\le 1000\),\(0\le d_i\le 200000\)
Solve
问题转变就是有多个任务,满足\(\exist y\le d_i\),使得\(s_ia^y\equiv x\mod{P}\),变形一下就是\(a^y\equiv xs^{-1}\mod{P}\)。所以可以求出每个\(a^i \% P\)的值,然后记录可以变成这个值的最小的天数\(d\),如果一个任务的\(d_i\)大于这个最小的\(d\),那么就可以达到。
Code
#include <iostream>
#include <unordered_map>
#define ll long long
using namespace std;
const int N=10005;
const int D=200005;
int P,a,n,q;
int s[N],d[N],apw[D],inv[N];
inline int power(int x,int y){
int res=1;
while(y){
if(y&1) res=(ll)res*x%P;
x=(ll)x*x%P;
y>>=1;
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
cin>>P>>a>>n>>q;
int mx=0;
for(int i=1;i<=n;i++){
cin>>s[i]>>d[i];
mx=max(mx,d[i]);
inv[i]=power(s[i],P-2);
}
unordered_map<int,int>hs;
apw[0]=1;
hs[apw[0]]=0;
int limit=min(mx,P-2);
for(int i=1;i<=limit;i++){
apw[i]=(ll)apw[i-1]*a%P;
if(hs.find(apw[i])==hs.end()){
hs[apw[i]]=i;
}
}
int ans;
while(q--){
int x;
cin>>x;
ans=0;
if(x==0){
for(int i=1;i<=n;i++){
if(s[i]==0) ans++;
}
cout<<ans<<'\n';
continue;
}
for(int i=1;i<=n;i++){
if(s[i]==0) continue;
int t=(ll)x*inv[i]%P;
if(hs.count(t) && hs[t]<=d[i]) ans++;
}
cout<<ans<<'\n';
}
}
return 0;
}
Map(计算几何、巴拿赫不动点)
Problem
给定二维平面中两个相似矩形,保证小矩形包含在大矩形中,问不动点坐标(巴拿赫不动点)
Solve
我们先把一个地图看做一个直角坐标系,在这个坐标系里面表示不动点点\(P\),取垂直的两条边作基向量即可,可以得到两个等式
然后可以得到
作差可以得到
然后对应分量相等得到两个方程组解\(\lambda\)和\(\mu\)即可
Code
乘法运算的时候不要用double
,因为这个一直T
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct Point{
int x,y;
Point operator + (const Point&t)const{
return {x+t.x,y+t.y};
}
Point operator - (const Point&t)const{
return {x-t.x,y-t.y};
}
}M[5],m[5];
typedef Point Vector;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout<<fixed<<setprecision(6);
int T;
cin>>T;
while(T--){
for(int i=1;i<=4;i++){
int x,y;
cin>>x>>y;
M[i]={x,y};
}
for(int i=1;i<=4;i++){
int x,y;
cin>>x>>y;
m[i]={x,y};
}
Vector AB=M[2]-M[1],AD=M[4]-M[1];
Vector ab=m[2]-m[1],ad=m[4]-m[1];
Vector Oa=m[1],OA=M[1];
int x1=(AB-ab).x,y1=(AB-ab).y;
int x2=(AD-ad).x,y2=(AD-ad).y;
int x=(Oa-OA).x,y=(Oa-OA).y;
double t1=(x*y2-y*x2)/(double)(x1*y2-x2*y1);
double t2=(x*y1-y*x1)/(double)(x2*y1-x1*y2);
double Px=OA.x+AB.x*t1+AD.x*t2,Py=OA.y+AB.y*t1+AD.y*t2;
cout<<Px<<" "<<Py<<'\n';
}
return 0;
}
Planar graph(图论、最大生成树)
Problem
给定一个平面图。平面图围被若干条边分成若干个平面。把每个平面看做城市,你需要在边上建桥,使得城市之间可以互通。问最少需要建多少个桥,可以使得每个面之间可以相互到达,并且要求建立桥的边的字典序最小。
Solve
建立桥可以看做是断边操作,发现断完边之后就图上是没有环的,变成了一棵树,而要求断开的边字典序要最小,所以按照把边的序号当做边权,求一个最大生成树,把不在树中的边加入答案即可
Code
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct edges{
int u,v;
}e[N];
int f[N];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[i]={u,v};
}
vector<int>ans;
for(int i=m;i>=1;i--){
int u=e[i].u,v=e[i].v;
int fu=find(u),fv=find(v);
if(fu==fv){
ans.push_back(i);
continue;
}
f[fu]=fv;
}
cout<<ans.size()<<'\n';
reverse(ans.begin(), ans.end());
for(auto x:ans) cout<<x<<" ";
cout<<'\n';
}
}
Find different(数论、群论)
Loop(贪心)
Problem
给定一个长度为\(n\)的序列,可以进行\(k\)次操作,每次操作可以选择一个区间\([L,R]\),然后把这个区间的数循环左移\(1\)位。问最后可以得到的最大字典序的序列是多少
Solve
字典序最大,就是前面的数字尽可能大,降序尽可能多。操作可以看做每次可以把一个前面的数放在后面的任意位置,显然,我们要尽可能把前面小的数字放到后面。比如如果存在\(a_{i-1}\lt a_i\),那么\(a_{i-1}\)可能放到后面会更好,然后继续检查\(a_{i-2}\),而我们要求字典序最大,所以从前面往后检查是更优的,所以每次可以放就一定放到后面并且消耗一次操作,知道无法操作就停止检查,这时我们可以得到一个保留序列和删除序列,然后把删除序列中的数大贪心地插入到保留序列里面即可
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,k;
cin>>n>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
vector<int>rem;
priority_queue<int> del;
for(int i=1;i<=n;i++){
while(rem.size() && rem.back()<a[i] &&k){
del.push(rem.back());
rem.pop_back();
k--;
}
rem.push_back(a[i]);
}
vector<int>b;
rem.push_back(-1e9);
del.push(-1e9);
int id=0;
while(int(b.size())<n){
if(rem[id]>=del.top()) b.push_back(rem[id++]);
else b.push_back(del.top()),del.pop();
}
for(int i=0;i<n;i++){
cout<<b[i];
if(i!=n-1) cout<<" ";
else cout<<"\n";
}
}
}