2022“杭电杯”中国大学生算法设计超级联赛(6)

2 篇文章 0 订阅
2 篇文章 0 订阅

Maex 贪心

链接:Maex

题意:

  • 给定一个有 n n n个点的树,编号 1 − n 1-n 1n,每个节点都有一个自然数权值 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(1T10)  n(1n5105)

分析:
如果节点 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) lr  (1lrn),将这段区间内的数往左平移一位, 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(1T100)  n,k(1n,k300000))

分析:

  • 每次左移一段区间相等于把区间左端点的数往后放,操作次数有限且字典序最大,对于每次操作,一定要贪心的从左往右找,找到一对 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 k1次找到的数有关,当找到 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} mAB + + + n ∗ A D ⃗ n*\vec{AD} nAD
又因为 m m m是由 M M M等比例缩小的,有 a p ⃗ \vec{ap} ap = = = m ∗ a b ⃗ m*\vec{ab} mab + + + n ∗ a d ⃗ n*\vec{ad} nad
相减得到: 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

链接:Shinobu loves trip

题意:

  • S很爱旅行,给定一个质数 p p p和常数 a a a,如果S当前在点 i i i,那么他可以通过花费一天的时间到达点 i ∗ a i*a ia% 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(1T5)
  • 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(2a<P1000000007,1n1000,1q1000)
  • 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(0si<P,1di200000)xi(0xi<P)

分析:

  • 如果预处理出来每个计划能到的所有点,复杂度 O ( d ∗ n ) O(d*n) O(dn)不能接受,但是我们可以快速的判断出来计划 i i i是否到达了点 x x x,通过预处理出来 a 0 − a 200000 a^{0}-a^{200000} a0a200000,对于第 i i i个计划有: s ∗ a k s*a^k sak% 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 0200000次方% 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;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值