10月16日考试 题解(模拟+树形DP+SG函数+最短路+线段树)

T1 三角形

题目大意:给定一个三个顶点都在网格图格点上的三角形,问在三角形内和边上的格点个数。$x,yleq 10^9$

先按照横坐标排序。考虑先框出能恰好包住这个三角形的矩形,然后大减小。有一个重要的性质:网格图内一个矩形的对角线能把矩形内所有的格点(不算对角线上的)恰好分成一半。设其对角线斜率为$frac{y}{x}$,那么在对角线上的格点个数为$gcd(x,y)+1$。于是我们可以处理锐角三角形。对于钝角三角形要特判:因为已经满足横坐标单调不降,所以只要看中间点的纵坐标是否满足要求即可;然后根据考场上发现的规律,只需在$(x3-x2) imes (y2-y1)$和$(x1-x2) imes (y3-y2)$中取较小值即可。时间复杂度$O(nlog n)$


一开始没注意正负号挂了好多分QAQ……

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int x[5],y[5],T;
struct node{
    int x,y;
}a[5];
inline bool cmp(node x,node y){
    return x.x==y.x?x.y<y.y:x.x<y.x;
}
inline int gcd(int x,int y)
{
    if(!y) return x;
    return gcd(y,x%y);
}
inline bool judge(int a,int b,int c){
    return 1ll*(b-a)*(c-a)<0;
}
signed main()
{
    cin>>T;
    while(T--)
    {
        int n=0,m=0;
        for (int i=1;i<=3;i++)
            scanf("%lld%lld",&a[i].x,&a[i].y);
        sort(a+1,a+4,cmp);
        for (int i=1;i<=3;i++)
            for (int j=i+1;j<=3;j++)
                n=max(n,abs(a[i].x-a[j].x)),
                m=max(m,abs(a[i].y-a[j].y));
        int sum=(n+1)*(m+1);
        for (int i=1;i<=3;i++)
            for (int j=i+1;j<=3;j++)
            {
                int xx=abs(a[i].x-a[j].x),yy=abs(a[i].y-a[j].y);
                int tot=((xx+1)*(yy+1)-gcd(xx,yy)-1)/2;
                sum-=tot;
            }
        if (judge(a[2].y,a[1].y,a[3].y))
        {
            int xx=1ll*(a[3].x-a[2].x)*(a[2].y-a[1].y);
            int yy=1ll*(a[2].x-a[1].x)*(a[3].y-a[2].y);
            xx=abs(xx),yy=abs(yy);
            if (xx>yy) xx=yy;
            sum-=xx;
         }
        printf("%lld
",sum);
    } 
    return 0;
}


T2 二叉树

题目大意:给定一棵完全二叉树,根节点为$1$。Alice控制距离根节点为奇数的点,Bob控制距离根节点为偶数的点。两个人可以指定每个点的重儿子。对于每个叶子$x$节点有二元组$(c,d)$,表示从根节点沿着重链走到$x$时,Alice能获得点数$c$,Bob能获得点数$d$。定义一种均衡的策略为:当$Bob$不改变策略时,无论Alice怎样改变策略,得到的点数都不大于原来的点数;对于Bob同理。$c,din [1,k]$,问均衡策略的方案数。$nleq 5000$

正解是树形DP,但是数据出锅了。再看看吧。

T3 森林大战

题目大意:给定一些森林,要求每个连通块内选取一些点为根,每个根上放一棋子;两个人轮流操作,将棋子像离根更远的方向移动。不能操作的人输。问先手必败的方案数。$nleq 10^5$

显然要用SG函数。暑假没学好博弈论,现在吃大亏……

考虑计算一棵有根树的SG函数,不难发现一个节点SG函数值是子节点SG函数值的mex。考虑一下SG函数的上界,发现一个SG函数值为$t$的节点至少有$t$个子树,所以SG函数值上界是$log n$。对于无根树,我们需要计算出每个节点作为根节点的SG函数值。这个可以换根DP得到。对于多棵树的SG函数值就是每棵树SG函数值的异或和。简单DP即可。时间复杂度$O(nlog n)$。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=100005;
const int MX=17;
const int p=998244353;
int num[N][MX],val[N],g[MX*2],f[MX*2],vis[N],n,m;
int head[N],cnt;
struct node{
    int next,to;
}edge[N*2];
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*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int from,int to)
{
    edge[++cnt]=(node){head[from],to};
    head[from]=cnt;
}
inline int calc(int x)
{
    for (int i=0;i<=16;i++)
        if (!num[x][i]) return i;
}
inline void dp(int v){
    for (int i=0;i<=32;i++)
        g[i^v]=(g[i^v]+f[i])%p;
}
inline void dfs1(int x)
{
    vis[x]=1;
    for (int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (vis[to]) continue;
        dfs1(to);
        num[x][val[to]]++;
    }
    val[x]=calc(x);
}
inline void dfs2(int x,int f)
{
    val[x]=calc(x);
    dp(val[x]);
    for (int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (to==f) continue;
        --num[x][val[to]];
        int newval=calc(x);
        ++num[x][val[to]];
        ++num[to][newval];
        dfs2(to,x);
    }
}
int main()
{
    n=read();m=read();
    f[0]=1;
    for (int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
    for (int i=1;i<=n;i++)
        if (!vis[i]){
            memset(g,0,sizeof(g));
            dfs1(i);dfs2(i,0);
            memcpy(f,g,sizeof(f));
        }
    printf("%d",f[0]);
    return 0;
}

T4 费用

题目大意:给定一张无向连通图,多次询问。每次改变一条边的权值,问从$1$到$n$的最短路长度。每次询问独立。$n,m,qleq 2 imes 10^5$。

原题:CF1163F

考虑到对于一条边的修改,只有4种情况:

1.原边在最短路上,变大了;

2.原边在最短路上,变小了;

3.原边不在最短路上,变大了;

4.原边不在最短路上,变小了。

2和3显然。对于4,我们从$1$和$n$分别跑一次最短路,可以求出经过这条边的最短路长度。将这个与原最短路长度作比较即可。上述复杂度都是$O(1)$的。

现在只剩1了,也是最麻烦的地方。我们可以发现一些结论:令最短路为$E$,其路径为$E_1,E_2,cdots,E_k$,对于不在$E$上的点$u$,$1$到$u$的最短路必定会使用$E$上的一段前缀,记为$l$;同理对于不在$E$上的点$v$,到$n$最短路必定会使用$E$的一段后缀,记为$r$。特别的,对于在$E$上的点,$l$为上一个点连接自己的边的编号,$r$为连接$E$上下一个点的边的编号。

对于不在$E$上的点,我们可以从$1$和$n$跑两次最短路,分别求出$l,r$。现在考虑解决问题1:不难发现答案为修改后的最短路长度和不经过这条边的最短路长度的较小值。于是问题转化成如何快速求出不经过某条边的最短路。考虑使用线段树。树上区间$[l,r]$表示不经过$[l,r]$内边的最短路长度。有了之前求的前缀和后缀,我们很容易可以以此更新线段树,从而可以在$O(log n)$的时间内解决问题。时间复杂度$O((n+m+q)log n)$。

细节不少,将就着看看吧QAQ

代码:

#include<cstdio>
#include<iostream>
#include<queue>
#define ll long long
using namespace std;
const int N=200005;
const ll inf=1e18;
ll dis[2][N],t[N*4];
bool on[N];
int idx[N],lstvis[N],tot,l[N],r[N],n,m,Q;
int head[N],cnt;
struct edge{
    int u,v,d;
}a[N];
struct e{
    int id,next,to;
    ll dis;
}edge[N*2];
struct node{
    int pos;ll dis;
    bool operator < (const node &x) const{
        return x.dis<dis;
    }
};
priority_queue<node> q;
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*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int id,int from,int to,ll dis)
{
    edge[++cnt].id=id;
    edge[cnt].next=head[from];
    edge[cnt].to=to;
    edge[cnt].dis=dis;
    head[from]=cnt;
}
inline void dijkstra(int s,int op,int f)
{
    for (int i=1;i<=n;i++) dis[op][i]=inf;
    dis[op][s]=0;q.push((node){s,0});
    while(!q.empty())
    {
        node fa=q.top();q.pop();
        int x=fa.pos;
        if (fa.dis>dis[op][x]) continue;
        for (int i=head[x];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (dis[op][to]>fa.dis+edge[i].dis)
            {
                dis[op][to]=fa.dis+edge[i].dis;
                lstvis[to]=edge[i].id;
                if (f==1&&!on[to]) l[to]=l[x];
                if (f==2&&!on[to]) r[to]=r[x];
                q.push((node){to,dis[op][to]});
            }
        }
    }
}
inline void pre_work()
{
    on[1]=1;
    l[1]=r[1]=0;
    int cur=1;
    for (int i=1;cur!=n;i++)
    {
        int id=lstvis[cur];
        idx[id]=i;
        ++tot;
        cur=a[id].u^a[id].v^cur;
        on[cur]=1;
        l[cur]=r[cur]=i;
    }
}
inline void build(int k,int l,int r)
{
    t[k]=inf;
    if (l==r) return;
    int mid=(l+r)>>1;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
}
inline void update(int k,int l,int r,int ql,int qr,ll v)
{
    if (ql>qr) return;
    if (ql<=l&&r<=qr){
        t[k]=min(t[k],v);
        return;
    }
    int mid=(l+r)>>1;
    if (ql<=mid) update(k*2,l,mid,ql,qr,v);
    if (qr>mid) update(k*2+1,mid+1,r,ql,qr,v);
}
inline ll query(int k,int l,int r,int x)
{
    if (l==r) return t[k];
    int mid=(l+r)>>1;
    ll res=t[k];
    if (x<=mid) res=min(res,query(k*2,l,mid,x));
    else res=min(res,query(k*2+1,mid+1,r,x));
    return res;
}
int main()
{
    n=read();m=read();Q=read();
    for (int i=1;i<=m;i++)
    {
        a[i].u=read(),a[i].v=read(),a[i].d=read();
        add(i,a[i].u,a[i].v,a[i].d);
        add(i,a[i].v,a[i].u,a[i].d);
        idx[i]=-1;
    }
    dijkstra(n,1,0);
    pre_work();
    dijkstra(1,0,1);
    dijkstra(n,1,2);
    build(1,1,tot);
    for (int i=1;i<=m;i++)
    {
        if (idx[i]==-1)
        {
            update(1,1,tot,l[a[i].u]+1,r[a[i].v],dis[0][a[i].u]+dis[1][a[i].v]+a[i].d);
            update(1,1,tot,l[a[i].v]+1,r[a[i].u],dis[0][a[i].v]+dis[1][a[i].u]+a[i].d);
            
        }
    }
    while(Q--)
    {
        ll ans=dis[0][n];
        int id=read(),len=read();
        if (idx[id]!=-1){
            ans=dis[0][n]-a[id].d+len;
            if (len>a[id].d){
                ans=min(ans,query(1,1,tot,idx[id]));
            }
        }else{
            if (len<a[id].d){
                ans=min(ans,dis[0][a[id].u]+len+dis[1][a[id].v]);
                ans=min(ans,dis[0][a[id].v]+len+dis[1][a[id].u]);
            }
        }
        printf("%lld
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13829710.html