Maex 贪心
链接:Maex
题意:
- 给定一个有 n n n个点的树,编号 1 − n 1-n 1−n,每个节点都有一个自然数权值 a a a,每个点的权值都是不同的,但是没有给出权值,节点i的 b i b_i bi 为不属于该节点的子树的所有权值的最小非负整数
- 求 ∑ i = 1 n b i ∑ i=_1^n b i ∑i=1nbi的最大值,多组数据 T ( 1 ≤ T ≤ 10 ) n ( 1 ≤ n ≤ 5 ⋅ 1 0 5 ) T(1≤T≤10)~~n \left( 1 \le n \le 5 \cdot 10^5 \right) T(1≤T≤10) n(1≤n≤5⋅105)
分析:
如果节点
u
u
u的子树中没有权值为0的点,那么该子树上所有的点的
b
b
b都为
0
0
0,如果有权值为0的点,那么
b
u
b_u
bu的值最大可以取到
c
n
t
u
cnt_u
cntu,即子树u上的节点的个数,找一下每个点能取到的最大值,并通过
s
u
m
[
u
]
=
m
a
x
(
s
u
m
[
u
]
,
s
u
m
[
j
]
+
c
n
t
[
u
]
)
sum[u]=max(sum[u],sum[j]+cnt[u])
sum[u]=max(sum[u],sum[j]+cnt[u])即可求出。
#include<iostream>
#include<cstring>
#include<algorithm>
#define x first
#define y second
#define int long long
using namespace std;
typedef long long ll;
const int N=5e5+10,M=2*N;
int n,m;
int cnt[N],sum[N];
int e[M],ne[M],idx,h[N];
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int dfs1(int u,int fa)
{
cnt[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa) continue;
cnt[u]+=dfs1(j,u);
}
return cnt[u];
}
void dfs2(int u,int fa)
{
sum[u]=cnt[u];
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa) continue;
dfs2(j,u);
sum[u]=max(sum[u],sum[j]+cnt[u]);
}
}
void solve()
{
cin>>n;
idx=0;
for(int i=1;i<=n;i++)
{
h[i]=-1;
cnt[i]=sum[i]=0;
}
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d %d",&a,&b);
add(a,b);
add(b,a);
}
dfs1(1,-1);
dfs2(1,-1);
cout<<sum[1]<<endl;
}
signed main()
{
int t;
t=1;
cin>>t;
for(int i=1;i<=t;i++)
{
solve();
}
return 0;
}
Loop 贪心
链接:Loop
题意:
- 给定一个长度为
n
n
n的序列,必须对这个序列进行
k
k
k次操作,每次操作可以选择一段区间
l
−
r
(
1
≤
l
≤
r
≤
n
)
l-r ~~(1≤l≤r≤n)
l−r (1≤l≤r≤n),将这段区间内的数往左平移一位,
a
[
l
]
a[l]
a[l]到
a
[
r
]
a[r]
a[r]的位置上,求
k
k
k次操作后能得到的字典序最大的序列是多少。
多组数据 T ( 1 ≤ T ≤ 100 ) n , k ( 1 ≤ n , k ≤ 300000 ) ) T (1≤T≤100 )~~n,k (1≤n,k≤300000)) T(1≤T≤100) n,k(1≤n,k≤300000))
分析:
- 每次左移一段区间相等于把区间左端点的数往后放,操作次数有限且字典序最大,对于每次操作,一定要贪心的从左往右找,找到一对 a [ i ] < a [ i + 1 ] a[i]<a[i+1] a[i]<a[i+1]的数,那么将 a [ i ] a[i] a[i]放到后面后形成的字典序一定是比当前最优的。
- 至于 a [ i ] a[i] a[i]放在哪里,与之后的 k − 1 k-1 k−1次找到的数有关,当找到 k k k个数后,对找到的 k k k个数与剩余的原序列做一次归并即可。
- 这里维护剩余的 a a a序列,与维护单调栈类似。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define x first
#define y second
//#define int long long
using namespace std;
typedef long long ll;
const int N=3e5+10,M=2*N;
int n,m,k;
int a[N],b[N],cnt;
int res[N];
void solve()
{
cnt=0;
cin>>n>>k;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
priority_queue<int>heap;
for(int i=1;i<=n;i++)
{
while(cnt&&k&&a[i]>b[cnt])//在没有找到k个数之前b内的序列是递减的
{
heap.push(b[cnt]);
cnt--;
k--;
}
b[++cnt]=a[i];//维护剩余的原序列
}
int l=0;
for(int i=1;i<=cnt;i++)
{
if(!heap.size())
{
res[++l]=b[i];
continue;
}
if(b[i]>=heap.top())
res[++l]=b[i];
else
{
while(heap.size()&&heap.top()>b[i])
{
res[++l]=heap.top();
heap.pop();
}
res[++l]=b[i];
}
}
while(heap.size()) res[++l]=heap.top(),heap.pop();
for(int i=1;i<=n;i++)
{
printf("%d",res[i]);
if(i!=n) printf(" ");
}
cout<<endl;
}
signed main()
{
int t;
t=1;
cin>>t;
for(int i=1;i<=t;i++)
{
solve();
}
return 0;
}
Planar graph 生成树
链接:Planar graph
题意:
给定一个图,图中的一些边将整个平面分成了许多相互隔离的区域,可以在边上建桥,使得所有的区域能连通。输出字典序最小建桥的解决方案。
分析:
- 已知的是树是没有环的,不会将一个平面分成隔离的区域,因此问题就转化成了去掉哪些边可以使得图变成一棵树,也就是生成树。
- 要想去掉的边字典序最小,已知的是去掉的边的数量是固定的,因此只要树中的边编号越大越好,剩下的边的编号组成的字典序就是最小的,因此按照编号作为权值求 M B T MBT MBT即可。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define x first
#define y second
//#define int long long
using namespace std;
typedef long long ll;
const int N=3e5+10,M=2*N;
struct Edge
{
int a,b,idx;
bool operator <(const Edge &e) const
{
return idx>e.idx;
}
}e[N];
int n,m,f[N],cnt;
bool st[N];
int get(int x)
{
if(x!=f[x]) f[x]=get(f[x]);
return f[x];
}
void kruskal()
{
for(int i=1;i<=m;i++)
{
int a=e[i].a,b=e[i].b;
int fa=get(a),fb=get(b);
if(fa!=fb)
{
f[fa]=fb;
st[e[i].idx]=true;
cnt++;
}
}
}
void solve()
{
cnt=0;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
f[i]=i;
}
for(int i=1;i<=m;i++)
{
st[i]=false;
int a,b;
scanf("%d %d",&a,&b);
e[i]={a,b,i};
}
sort(e+1,e+m+1);
kruskal();
cout<<m-cnt<<endl;
for(int i=1;i<=m;i++)
{
if(!st[i]) cout<<i<<" ";
}
cout<<endl;
}
signed main()
{
int t;
t=1;
cin>>t;
for(int i=1;i<=t;i++)
{
solve();
}
return 0;
}
Map 数学
链接:Map
题意:
给定一个矩形
M
M
M和一个矩形
m
m
m,其中
m
m
m是
M
M
M缩小
k
k
k倍后形成的,将
m
m
m任意放在
M
M
M的内部,一定会有一个点在
m
m
m内部,且这个点的位置在矩形
M
M
M和
m
m
m的相对位置是一样的,求这个点的坐标。
分析:
设不动点为
p
p
p,有
A
p
⃗
\vec{Ap}
Ap
=
=
=
m
∗
A
B
⃗
m*\vec{AB}
m∗AB
+
+
+
n
∗
A
D
⃗
n*\vec{AD}
n∗AD
又因为
m
m
m是由
M
M
M等比例缩小的,有
a
p
⃗
\vec{ap}
ap
=
=
=
m
∗
a
b
⃗
m*\vec{ab}
m∗ab
+
+
+
n
∗
a
d
⃗
n*\vec{ad}
n∗ad
相减得到:
A
a
⃗
\vec{Aa}
Aa
=
m
(
=m(
=m(
A
B
⃗
\vec{AB}
AB
−
-
−
a
b
⃗
\vec{ab}
ab
)
+
n
(
)+n(
)+n(
A
D
⃗
\vec{AD}
AD-
a
d
⃗
\vec{ad}
ad
)
)
)
各个点的坐标已知可以求得
m
m
m和
n
n
n,再带入的第一或第二个等式就能求出
p
p
p点坐标。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define x first
#define y second
//#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=3e5+10,M=2*N;
int n,m,f[N],cnt;
bool st[N];
PII p[10];
PII GetVector(PII a,PII b)
{
PII res={b.x-a.x,b.y-a.y};
return res;
}
void solve()
{
for(int i=1;i<=8;i++) cin>>p[i].x>>p[i].y;
PII Aa=GetVector(p[1],p[5]);
PII AB=GetVector(p[1],p[2]);
PII ab=GetVector(p[5],p[6]);
PII AD=GetVector(p[1],p[4]);
PII ad=GetVector(p[5],p[8]);
PII d1={AB.x-ab.x,AB.y-ab.y};
PII d2={AD.x-ad.x,AD.y-ad.y};
double a=Aa.x,b=Aa.y,c=d1.x,d=d1.y,e=d2.x,f=d2.y;
//cout<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<e<<" "<<f<<endl;
double cy=((b*c-a*d)*1.0/(c*f-d*e));
double cx=(a-e*cy)/c;
// cout<<cy<<" "<<cx<<endl;
double resx=p[1].x+cx*AB.x+cy*AD.x;
double resy=p[1].y+cx*AB.y+cy*AD.y;
printf("%.6lf %.6lf\n",resx,resy);
}
signed main()
{
int t;
t=1;
cin>>t;
for(int i=1;i<=t;i++)
{
solve();
}
return 0;
}
Shinobu loves trip
题意:
- S很爱旅行,给定一个质数 p p p和常数 a a a,如果S当前在点 i i i,那么他可以通过花费一天的时间到达点 i ∗ a i*a i∗a% p p p 。
- 现在S有 n n n个计划来旅游,每个计划有一个 s s s和 d d d,表示 S S S的起点和旅游的天数,现在有 q q q个询问,对于一个询问,给一个地点 x x x,问S有多少个旅游计划会经过地点 x x x。
- 多组数据 T ( 1 ≤ T ≤ 5 ) T (1≤T≤5 ) T(1≤T≤5)
- P , a , n , q ( 2 ≤ a < P ≤ 1000000007 , 1 ≤ n ≤ 1000 , 1 ≤ q ≤ 1000 ) P, a, n, q(2≤a<P≤1000000007,1≤n≤1000,1≤q≤1000) P,a,n,q(2≤a<P≤1000000007,1≤n≤1000,1≤q≤1000)
- s i , d i ( 0 ≤ s i < P , 1 ≤ d i ≤ 200000 ) x i ( 0 ≤ x i < P ) s_i, d_i(0≤s_i<P,1≤d_i≤200000) x_i(0≤x_i<P) si,di(0≤si<P,1≤di≤200000)xi(0≤xi<P)
分析:
- 如果预处理出来每个计划能到的所有点,复杂度 O ( d ∗ n ) O(d*n) O(d∗n)不能接受,但是我们可以快速的判断出来计划 i i i是否到达了点 x x x,通过预处理出来 a 0 − a 200000 a^{0}-a^{200000} a0−a200000,对于第 i i i个计划有: s ∗ a k s*a^k s∗ak% p = x p=x p=x,已知 s , a , p , x s,a,p,x s,a,p,x,可以求出来 a k a^k ak,如果 a k a^k ak在预处理出来的点且 k < = d k<=d k<=d,意味着计划 i i i是经过点 x x x的。
- 因此只需要预处理 a a a的 0 − 200000 0-200000 0−200000次方% p p p,并记录一下出现 m p [ n u m ] mp[num] mp[num]的最小次方即可。因为要求逆元,所以要对 s = 0 s=0 s=0的情况特判。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<unordered_map>
#define x first
#define y second
//#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=1010,M=2*N;
unordered_map<int,int>mp;
PII t[N];
int inf[N];
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1) res=1ll*res*a%p;
a=1ll*a*a%p;
k>>=1;
}
return res;
}
void solve()
{
mp.clear();
int p,a,n,q;
cin>>p>>a>>n>>q;
for(int i=0;i<=200000;i++)
{
int x=qmi(a,i,p);
if(!mp.count(x)) mp[x]=i;
}
for(int i=1;i<=n;i++)
{
cin>>t[i].x>>t[i].y;
if(t[i].x)
inf[i]=qmi(t[i].x,p-2,p);
}
for(int i=1;i<=q;i++)
{
int ex;
cin>>ex;
int cnt=0;
if(ex==0)
{
for(int j=1;j<=n;j++)
if(t[j].x==0) cnt++;
}
else
{
for(int j=1;j<=n;j++)
{
if(!t[j].x) continue;
int d=t[j].y;
int c=1ll*ex*inf[j]%p;
if(mp.count(c)&&d>=mp[c])
{
cnt++;
}
}
}
cout<<cnt<<endl;
}
}
signed main()
{
int t;
t=1;
cin>>t;
for(int i=1;i<=t;i++)
{
solve();
}
return 0;
}