2020-2021 Winter Petrozavodsk Camp, UPC contest - 补题(6/12) Petrozavodsk 冬令营

C. Cartesian MST 最小生成树,联通块,并查集

E. Even Intervals 莫队,权值线段树,思维

I. Interesting Scoring Systems 思维,贪心

J. Joyful Numbers 打表,思维

K. Königsberg Bridges  桥,树的直径,Tarjan

L. Long Grid Covering 推式子,矩阵快速幂

 C. Cartesian MST

笛卡尔积后每个点变成了二维组合。但其边权仍然来自原始的V,G

G中一条边(u1,u2) ,如(3,4) 对应新图中3,4行同一列的两者连边

V同理,对应两列同行的连边

现在我们要为这n*m个点加边生成新图的最小生成树

考虑按照边权从低到高排序,类似于最小生成树。

加入G中边(3,4),引起两行被加边

加入G中边(2,4),又引起两行被加边

...

可以发现,只加G边的时候,同一列的连边

加入V边的时候,如合并2,5列,我们发现,原来行合并的两个点,即(3,2) (5,2)两个红点

会分别和(5,2) (5,5)两个绿点合并,然而显然,我们只需要一个红点和一个绿点合并就够了。

也就是在行合并的时候,两个红点已经变成一个红点了,联通块个数-1

这样我们就分别记录行联通块个数,列联通块个数,每次合并,更新联通块个数即可。

合法性 这一过程是合法的,最终我们要连出n*m-1条边,我们先做n-1次行合并,连边(n-1)*m个

再进行m-1次列合并,连边m-1个(此时每一列的行联通块只剩下1个),(n-1)*m+m-1等于n*m-1

# include<bits/stdc++.h>
using namespace std;
typedef long long int ll;

struct node
{
    int id,b,e;
    ll val;
};

struct node s[1000000+10];
bool cmp(struct node x, struct node y)
{
    return x.val<y.val;
}
int fa1[1000000+10],fa2[1000000+10];
int getf1(int x)
{
    if(x==fa1[x])
        return x;
    else
    {
        fa1[x]=getf1(fa1[x]);
        return fa1[x];
    }
}
int getf2(int x)
{
    if(x==fa2[x])
        return x;
    else
    {
        fa2[x]=getf2(fa2[x]);
        return fa2[x];
    }
}
int main ()
{

    int n1,m1,n2,m2;
    cin>>n1>>m1>>n2>>m2;
    for(int i=1; i<=m1+m2; i++)
    {
       scanf("%d%d%lld",&s[i].b,&s[i].e,&s[i].val);
        s[i].b++;
        s[i].e++;
        if(i<=m1)
            s[i].id=1;
        else
            s[i].id=2;
    }
    for(int i=1; i<=n1; i++)
    {
        fa1[i]=i;
    }
    for(int i=1; i<=n2; i++)
    {
        fa2[i]=i;
    }
    ll cnt1=n1,cnt2=n2;
    sort(s+1,s+1+m1+m2,cmp);
    ll ans=0;
    for(int i=1; i<=m1+m2; i++)
    {
        if(s[i].id==1)
        {
            if(getf1(s[i].b)==getf1(s[i].e))
                continue;
            int t1=getf1(s[i].b);
            int t2=getf1(s[i].e);
            fa1[t1]=t2;
            ans+=(ll)(s[i].val)*(ll)cnt2;
            cnt1--;
        }
        else
        {
            if(getf2(s[i].b)==getf2(s[i].e))
                continue;
            int t1=getf2(s[i].b);
            int t2=getf2(s[i].e);
            fa2[t1]=t2;
            ans+=(ll)(s[i].val)*(ll)cnt1;
            cnt2--;

        }
    }
    cout<<ans;

    return 0;
}
 

 E. Even Intervals

n个数字,2e5次区间查询,每次查询的是区间[L,R]排序之后奇数位置之和(1开头)

用线段树来维护每个区间的取值情况,莫队进行动态修改,复杂度nsqrt(n)log(n) 20s时限够用。

线段树维护的是1--1e9的值域,单纯就线段树里一个区间来说,它的答案由左右儿子更新。

sum0代表区间以第二个数字开头,间隔1的和,sum1代表区间以第一个数字开头,间隔1的和。

当左儿子区间数字个数为奇数时

当前sum0为左区间sum0+右区间sum1 ,sum1为左区间sum1+右区间sum0 

左区间数字个数为偶数时同理

当前sum0为左区间sum0+右区间sum0

当前sum1为左区间sum1+右区间sum1

莫队中每次移动指针,都落实到线段树的单点修改即可

# include<bits/stdc++.h>
# pragma GCC optimize(2)
using namespace std;
typedef long long int ll;
# define mod 1000000007
int belong[500000+10],a[1000000+10];
struct node
{
    int id,l,r;
};
struct node s[1000000+10];
bool cmp(struct node x, struct node y)
{
    if(belong[x.l]!=belong[y.l])
        return belong[x.l]<belong[y.l];
    if(belong[x.l]%2)
        return x.r<y.r;
    return x.r>y.r;
}
struct t
{
    int l,r,cnt;
    ll sum1,sum0;
};

struct t tree[500000*60+10];
int tot=1;
int change(int root,int val,int pos,int L,int R)
{
    if(root==0)
    {
        tot++;
        root=tot;
    }
    if(L==R)
    {
        tree[root].cnt+=val;
        if(tree[root].cnt==0)
        {
            tree[root].sum1=tree[root].sum0=0;
        }
        else
        {
            tree[root].sum0=0;
            tree[root].sum1=pos;
        }
        return root;
    }

    int mid=(L+R)>>1;

    if(pos<=mid)
        tree[root].l=change(tree[root].l,val,pos,L,mid);
    else
        tree[root].r=change(tree[root].r,val,pos,mid+1,R);

    tree[root].cnt=tree[tree[root].l].cnt+tree[tree[root].r].cnt;

    if(tree[tree[root].l].cnt%2==1)
    {
        tree[root].sum1=(tree[tree[root].l].sum1+tree[tree[root].r].sum0)%mod;
        tree[root].sum0=(tree[tree[root].l].sum0+tree[tree[root].r].sum1)%mod;

    }
    else
    {
        tree[root].sum1=(tree[tree[root].l].sum1+tree[tree[root].r].sum1)%mod;
        tree[root].sum0=(tree[tree[root].l].sum0+tree[tree[root].r].sum0)%mod;
    }
    return root;
}
void add(int x)
{
   change(1,1,a[x],0,1e9);
}
void del(int x)
{
   change(1,-1,a[x],0,1e9);
}
ll ans[200000+10];
int main ()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<=500000*60;i++)
    {
        tree[i].l=tree[i].r=tree[i].sum1=tree[i].sum0=0;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    int siz=sqrt(n);
    int len=ceil((double)(n/siz));
    for(int i=1;i<=len;i++)
    {
        for(int j=(i-1)*siz+1;j<=i*siz;j++)
        {
            belong[j]=i;
        }
    }
   for(int i=1;i<=m;i++)
   {
       scanf("%d%d",&s[i].l,&s[i].r);
       s[i].l++;
       s[i].r++;
       s[i].id=i;
   }
   sort(s+1,s+1+m,cmp);
   int l=1,r=0;
   for(int i=1;i<=m;i++)
   {
       while(l<s[i].l)
       {
           del(l);
           l++;
       }
       while(l>s[i].l)
       {
           l--;
           add(l);
       }
       while(r<s[i].r)
       {
           r++;
           add(r);
       }
       while(r>s[i].r)
       {
           del(r);
           r--;
       }
       ans[s[i].id]=tree[1].sum1;
   }
   for(int i=1;i<=m;i++)
   {
       cout<<ans[i]<<'\n';
   }
    return 0;
}
 

 I. Interesting Scoring Systems

首先,这是两种评判规则下,同一场比赛的结果。

结论,1号人只要胜一场,全局胜场个数>=n-1,1号人就可以胜全部人。因为这是可以传递的。

所以问题就变成了,我们要在使结果满足两种评判规则下,全局胜场个数尽可能多。

每次从a[i],b[i]里面分别扣除2,3,当二者相等的时候,说明剩下的是平局场次,无法再扣除,因为如果继续扣除,二者必然不会再相等。这一点用数学知识直接运算。

另外,特判n=1,n=2,n=2的时候,2号人一定不能赢,否则2号人赢的一定是1号,不满足题意。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int a[1000000+10],b[1000000+10];
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

inline void write(ll x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
int main()
{

    int t;
    cin>>t;

    while(t--)
    {
        int n;
        cin>>n;
        int flag=0;
        for(int i=1; i<=n; i++)
        {
          a[i]=read();
            if(a[1]<2)
            {
                flag=1;
            }
        }
        for(int i=1; i<=n; i++)
        {
          b[i]=read();
            if(b[1]<3)
            {
                flag=1;
            }
        }
        if(n==1)
        {
            cout<<"YES"<<'\n';
            continue;

        }
        else if(n==2)
        {
            if(flag)
            {
                cout<<"NO"<<'\n';
                continue;
            }

            b[1]-=3;
            a[1]-=2;

            if(a[2]!=b[2])
            {
                cout<<"NO"<<'\n';
            }
            else
            {
                cout<<"YES"<<'\n';
            }
            continue;
        }
        if(flag)
        {
            cout<<"NO"<<'\n';
            continue;
        }
        a[1]-=2;
        b[1]-=3;
        ll sum=1;
        for(int i=1; i<=n; i++)
        {
            if(a[i]==b[i])
                continue;
            if(a[i]>b[i])
            {
                flag=1;
                break;
            }
            ll nowt1=a[i]/2,nowt2=b[i]/3;
            ll cha=b[i]-a[i];
            if(cha>min(nowt1,nowt2))
            {
                flag=1;
                break;
            }
            sum+=cha;
        }
        if(flag||sum<n-1)
        {
            cout<<"NO"<<'\n';
        }
        else
        {
            cout<<"YES"<<'\n';
        }
    }
    return 0;

}

 J. Joyful Numbers

打表发现,这些数字满足,第n个就是n*(n+1),那么对n,n+1分别进行sqrt(n)的质因数分解即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int prime[10000000+10];
bool not_prime[10000000+10];
int tot;
void init()
{
    for(int i=2; i<=10000000; i++)
    {
        if(!not_prime[i])
        {
            tot++;
            prime[tot]=i;
        }

        for(int j=1; j<=tot&&(ll)prime[j]*(ll)i<=10000000; j++)
        {
            not_prime[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                break;
            }
        }
    }
}
map<ll,int>mp;
set<int>s;
ll getcnt(ll x)
{
    ll ans=0;

    int pos=0;
    for(int i=1; i<=tot&&(ll)prime[i]*(ll)prime[i]<=x; i++)
    {
        if(x%prime[i]==0)
        {
            ans++;
            s.insert(prime[i]);
            while(x%prime[i]==0)
                x/=prime[i];
        }
        pos=i;
    }
    if(x>1)
        s.insert(x);
    return ans;
}

int main()
{

   init();
    int t;
    cin>>t;
    while(t--)
    {
        ll n;
        cin>>n;
        s.clear();
        getcnt(n);
        getcnt(n+1);
        cout<<s.size()<<'\n';

    }
    return 0;

}

 K. Königsberg Bridges

首先一个思路就是对每个联通块都求一个桥,然后把全部联通块都用单边相连,这样单边本身也是桥。难点在于,怎么样保证我们拥有全局一条经过桥的简单路径,这条路径必须只能经过路线上点一次。也就是一条链罢了。

无向图找桥的时候,我们在tarjan的基础上,加了一个fa,也就是之前访问的点,那么这其实就可以借鉴树求直径的套路,每个点设置dp值代表由这个点出发的两条最长链。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int dfn[1000000+10],timecnt,low[1000000+10];
vector<int>v[1000000+10];
int cnt,du[1000000+10],flag=0,dp[1000000+10],nowans;
void Tarjan(int u, int fa)
{
    dfn[u] = low[u] = ++timecnt;
    vector<int>temp;
    for (auto v:v[u])
    {
        if (v == fa)
        {
            continue;
        }
        if (!dfn[v])
        {
            Tarjan(v, u);
            low[u] = min(low[v], low[u]);

            if (low[v] > dfn[u])
            {
                dp[v]++;
            }
            temp.push_back(dp[v]);
        }
        else
        {
            low[u] = min(dfn[v], low[u]);
        }
    }
    sort(temp.begin(),temp.end(),greater<int>());
    if(temp.size()>=2)
    {
        nowans=max(nowans,temp[0]+temp[1]);
        dp[u]=temp[0];
    }
    else if(temp.size()>=1)
    {
        nowans=max(nowans,temp[0]);
        dp[u]=temp[0];
    }
}
int main()
{

    int n,m;
    cin>>n>>m;

    for(int i=1; i<=m; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        x++;
        y++;
        v[x].push_back(y);
        v[y].push_back(x);
        du[x]++;
        du[y]++;
    }
    int dan=0;
    for(int i=1; i<=n; i++)
    {
        if(du[i]==0)
        {
            dan++;
        }
        else if(dfn[i]==0)
        {
            nowans=0;
            Tarjan(i,0);
            dan++;
            cnt+=nowans;
        }
    }
    cout<<dan-1+cnt;
    return 0;

}

 

 L. Long Grid Covering

大力画图形推式子

令矩阵为

dpn dpn-1 dpn-2 s1(n) s2(n) s3(n)

dp[n]=dp[n-1]+dp[n-2]*2+dp[n-3]+s1*2+s2*2

 

 

 

 

#include <bits/stdc++.h>

using namespace std;
typedef long long int ll;
# define mod 1000000007
ll base[10][10],res[10][10],temp[10][10];
void init()
{
    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=6;j++)
        {
            base[i][j]=res[i][j]=0;
        }
    }
    base[1][1]=1;
    base[1][2]=2;
    base[1][3]=1;
    base[1][4]=2;
    base[1][5]=2;
    base[2][1]=1;
    base[3][2]=1;
    base[4][2]=1;
    base[4][6]=1;
    base[5][2]=1;
    base[5][4]=1;
    base[6][5]=1;
    for(int i=1;i<=6;i++)
    {
        res[i][i]=1;
    }
}
void clc()
{
    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=6;j++)
        {
            temp[i][j]=0;
        }
    }
}

void mulres()
{
    clc();

    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=6;j++)
        {
            for(int k=1;k<=6;k++)
            {
                temp[i][j]=(temp[i][j]+res[i][k]*base[k][j]%mod)%mod;
            }
        }
    }
    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=6;j++)
        {
            res[i][j]=temp[i][j];
        }
    }
}
void mulbase()
{
    clc();
    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=6;j++)
        {
            for(int k=1;k<=6;k++)
            {
                temp[i][j]=(temp[i][j]+base[i][k]*base[k][j]%mod)%mod;
            }
        }
    }

    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=6;j++)
        {
            base[i][j]=temp[i][j];
        }
    }
}
void qp(ll pow)
{
       while(pow)
       {
           if(pow&1)
            mulres();

            pow>>=1;
           mulbase();
       }
}
int main()
{


    int t;
    cin>>t;
    while(t--)
    {
        ll n;
        cin>>n;
        init();
        if(n<=3)
        {
            if(n==1)
            {
                cout<<1<<endl;
            }
            else if(n==2)
            {
                cout<<3<<endl;
            }
            else
            {
                cout<<10<<endl;
            }
        }
        else
        {
            qp(n-3);
            ll ans=((res[1][1]*10ll%mod+res[1][2]*3ll%mod)%mod+res[1][3])%mod;
            ans=(ans+res[1][4])%mod;
            ans=(ans+res[1][5]*2ll%mod)%mod;
            ans=(ans+res[1][6]%mod)%mod;
            cout<<ans<<endl;
        }
    }
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qinsanma and Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值