【纪中集训】2019.08.10【NOIP提高组】模拟 A 组TJ

T1

Description

在这里插入图片描述
在这里插入图片描述

Solution
  • 有待填坑……

T2

Description
  • 给定一个\(h(≤10)\)层、\(n(≤10)\)行、\(m(≤10)\)列的由泥土组成的立方体,挖开\((i,j,k)\)的泥土代价为\(a[i,j,k](\in[0,65536))\),挖开后就可以随意走这个点。一开始在第0层随便一个点,每次可以挖开他正下方、以及他同一层的四连通相邻点。
  • \(z\)层有\(K[z](≤9)\)个点必须经过。
  • 求最小代价。

    Solution
  • 分层斯坦纳树。但此题有些特殊,它是有向边,如果直接做则斯坦纳树的第一种转移(即状态不同的转移)应该是要枚举一条边的;因此我们可以不连边,而是走到一个点就加上它的点权。
  • 注意到还要考虑其他层的影响;因此我们可以自上而下(自下而上也是一样)地做,每层都新建一个特殊必经点,表示它当前层上面所有层的总和,然后要根据它第一次走到的点来定夺附加点权。
  • 这样做的话,时间复杂度就是\(O(nm\sum_{z=1}^h2^{K[z]})\)的了。

    Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MIN(x,y) if(x>y)x=y
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

const int H=11,N=H*H,inf=0x3f3f3f3f;
int h,n,m,n1,a[H][N],K,x,y,X[H],f[1024][N],g[N],hd,tl,d[N*N],ans;
bool p[N];

bool go(int x,int y) {return x%m^1&&y==x-1||x%m&&y==x+1||y==x-m||y==x+m;}

void spfa(int dep,int S)
{
    while(hd<tl)
    {
        int x=d[++hd];
        fo(y,1,n1)
            if(!x||go(x,y))
            {
                int len=a[dep][y]+(!x&&dep>1?g[y]:0);
                if(f[S][y]>f[S][x]+len)
                {
                    f[S][y]=f[S][x]+len;
                    if(!p[y])
                    {
                        p[d[++tl]=y]=1;
                        if(f[S][d[h+1]]>f[S][y]) swap(d[hd+1],d[tl]);
                    }
                }
            }
        p[x]=0;
    }
}

int main()
{
    freopen("treasure.in","r",stdin);
    freopen("treasure.out","w",stdout);
    scanf("%d%d%d",&h,&n,&m), n1=n*m;
    fo(dep,1,h) fo(i,1,n1) scanf("%d",&a[dep][i]);
    fo(dep,1,h)
    {
        memset(f,63,sizeof f);
        scanf("%d",&K);
        fo(i,1,K) scanf("%d%d",&x,&y), X[i]=(x-1)*m+y, f[0][X[i]]=f[1<<i-1][X[i]]=a[dep][X[i]];
        X[++K]=0, f[0][0]=f[1<<K-1][0]=0;
        fo(S,1,(1<<K)-1)
        {
            for(int s=(S-1)&S; 233; s=(s-1)&S)
            {
                fo(x,1,n1) MIN(f[S][x],f[s][x]+f[S^s][x]-a[dep][x]);
                if(!s) break;
            }
            hd=tl=0;
            fo(i,0,n1) if(f[S][i]<inf) p[d[++tl]=i]=1;
            spfa(dep,S);
        }
        fo(i,1,n1) g[i]=f[(1<<K)-1][i];
    }
    ans=inf;
    fo(i,1,n1) MIN(ans,g[i]);
    printf("%d",ans);
}

T3

Description
  • 给定无限平面网格图上的\(N(≤100000)\)个黑格,其余格为白。保证所有黑格四连通,所有白格四连通。
  • 在一个黑格时,一步可以走到与它四连通相邻的黑格。
  • 求所有黑格两两间的最小步数。

    Solution
  • 这是IOI2012T4,顾昱洲写的TJ里有一种DP解法,但那种方法有些繁琐;这里讲一种撵爆顾昱洲更为简单的方法。
  • 考虑将黑格按横坐标剖分,即把一块横坐标相同且相邻的黑格压成一个点;如果有两块横坐标不同但相邻的黑格,就在它们对应的新点之间连边。这样一定会形成一棵树,我们可以直接在上面搞事情。
  • 但如果直接遍历这棵重构树求答案,我们并不知道对于之前的黑格,它走下来需要左右移动多少次(即纵坐标更改多少次),强行记录的话又过于繁琐。有一个很妙的思路是:我们可以把横坐标和纵坐标对答案的贡献分开处理。比如处理横坐标对答案的贡献时,就不管它左右移动,每次只上下移动,那就很好做了。
  • 时间复杂度的话,不算排序是\(O(n)\)的。

    Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef pair<int,int> P;

const int N=11e4,v[2]={-1,1};
int n,cnt,L[N],R[N],X[N],tot,a[N],sz[N];
P p[N];
struct edge{int v,t;}e[N<<1];
long long ans;

inline bool con(int x,int y) {return X[x]+1==X[y]&&L[x]<=R[y]&&L[y]<=R[x];}
inline void link(int x,int y)
{
    e[++tot]=(edge){y,a[x]}, a[x]=tot;
    e[++tot]=(edge){x,a[y]}, a[y]=tot;
}
void dfs(int x,int f)
{
    sz[x]=R[x]-L[x]+1;
    for(int i=a[x],y; y=e[i].v; i=e[i].t) if(y^f) dfs(y,x),sz[x]+=sz[y];
    ans+=1ll*sz[x]*(n-sz[x]);
}
void work()
{
    sort(p+1,p+n+1);
    cnt=0;
    fo(i,1,n) if(p[i-1].fi<p[i].fi||p[i-1].se+1<p[i].se) R[cnt]=p[i-1].se, L[++cnt]=p[i].se, X[cnt]=p[i].fi;
    R[cnt]=p[n].se;
    int j=1;
    memset(a,tot=0,sizeof a);
    fo(i,1,cnt)
    {
        if(j>cnt) break;
        for(; j<=cnt&&(X[j]<=X[i]||X[j]==X[i]+1&&!con(i+1,j)&&L[j]<=R[i]); j++) if(con(i,j)) link(i,j);
        if(con(i,j)) link(i,j);
    }
    dfs(1,0);
}

int main()
{
    freopen("city.in","r",stdin);
    freopen("city.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n) scanf("%d%d",&p[i].fi,&p[i].se);
    
    work();
    
    fo(i,1,n) swap(p[i].fi,p[i].se);
    work();
    
    printf("%lld",ans%int(1e9));
}

转载于:https://www.cnblogs.com/Iking123/p/11335819.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值