[九省联考2018]-Day1-一双木棋-IIIDX-秘密袭击

说在前面

模拟考,考了260巨开心hhhhh
T3正解是什么鬼啊QAQ…….当然是暴力出奇迹啊!


题目

LOJ传送门


T1

首先可以确定的是,这是一个不平等博弈问题,而且非0和(当然这并没有什么卵用)

我们仍然从决策状态来分析这个题!
根据题意,我们选择的方块一定是左上角的一部分,大概长这样:
这里写图片描述
选择部分的高度,从左往右递减。未选择部分的高度,从左往右递增
于是我们可以发现,其实有效局面非常的少,最多就是「长度为10的非下降子序列」这么多,也就是 C1020 C 20 10 (打表也可以知道,反正很少),我们可以把这些状态Hash下来
另一方面,一个局面能够到达的局面也很少(因为只能选拐点的格子),所以我们也可以预处理出转移状态

剩下就是决策问题了!如果说有两种分数同时决策的话,是很难办到的。于是可以转化成这样,A在开局就已经获得了全部的分数A。而每次B占领一个格子,就是要扣分,而A占领一个格子,得0分

于是现在变成了一个这样的问题:两个人轮流操作,一个人要使分数尽量大,一个人要使分数尽量少
这就很容易在转移的时候通过区分 取min还是取max 来达成了!

另:也可以用轮廓线的思路来考虑本题,做法是相似的

下面是代码

#include <ctime>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

const int Base1 = 137 , Base2 = 233 ;
const int Mod1 = 23333333 , Mod2 = 19260817 ;
int N , M , A[15][15] , B[15][15] , All , mp[15][15] ;
struct Hash_Table{
    int head[100008] , pre[200000] ;
    int val[200000] , val2[200000] , id[200000] , tp ;
    void Insert( int h1 , int h2 , int id_ ){
        int x = h1 % 100007 ;
        pre[++tp] = head[x] ;
        head[x] = tp ;
        val[tp] = h1 , val2[tp] = h2 ;
        id[tp] = id_ ;
    }
    int find( int h1 , int h2 ){
        int x = h1 % 100007 ;
        for( int i = head[x] ; i ; i = pre[i] )
            if( val[i] == h1 && val2[i] == h2 )
                return id[i] ;
        exit( 255 ) ;
    }
}hs ;

int head[200000] , tp , deg[200000] ;
struct Path{
    int pre , to , val ;
}p[ 184756*11+5 ] ;

void In( int t1 , int t2 , int t3 ){
    deg[t2] ++ ;
    p[++tp] = ( Path ){ head[t1] , t2 , t3 } ; head[t1] = tp ;
}

int hig[15] , id_c , mn[200000] ;
pair<long long,long long> getHash(){
    long long h1 = 1 , h2 = 1 ;  
    for( short i = 1 ; i <= M ; i ++ ){
        h1 = ( h1 + hig[i] ) * Base1 %Mod1 ;
        h2 = ( h2 + hig[i] ) * Base2 %Mod2 ;
    } return make_pair( h1 , h2 ) ;
}

void dfs_id( int dep , int lim ){
    if( dep == 0 ){
        id_c ++ ;
        pair<long long,long long> t = getHash() ;
        int tmp = N * M ;
        for( short i = 1 ; i <= M ; i ++ ) tmp -= hig[i] ;
        mn[id_c] = ( tmp & 1 ) ;
        //if ( tmp & 1 ) is 1 , then get min
        //else get max
        hs.Insert( t.first , t.second , id_c ) ;
        return ;
    } for( int i = 0 ; i <= lim ; i ++ )
        hig[dep] = i , dfs_id( dep - 1 , i ) ;
}

void dfs_Path( int dep , int lim ){
    if( dep == 0 ){
        id_c ++ ;
        pair<long long,long long> t ;
        for( short i = 1 ; i <= M ; i ++ ){
            if( hig[i] > hig[i-1] ){
                hig[i] -- ; t = getHash() ;
                In( hs.find( t.first , t.second ) , id_c , mp[ N-hig[i] ][i] ) ;
                hig[i] ++ ;
            }
        } return ;
    } for( int i = 0 ; i <= lim ; i ++ )
        hig[dep] = i , dfs_Path( dep - 1 , i ) ;
}


void preWork(){
    dfs_id( M , N ) ; id_c = 0 ;
    dfs_Path( M , N ) ;
}

int val[200005] , que[200005] , fr , ba ;
void solve(){
    for( int i = 1 ; i <= id_c ; i ++ )
        if( mn[i] ) val[i] = 0x3f3f3f3f ;
        else val[i] = -0x3f3f3f3f ;

    fr = 1 , ba = 0 ; val[1] = All ;
    que[++ba] = 1 ;// state 1 : 0 0 0 0 0 0 0 0 0...
    while( ba >= fr ){
        int u = que[fr++] , now = val[u] ;
        for( int i = head[u] ; i ; i = p[i].pre ){
            int v = p[i].to , delta = p[i].val ;
            if( mn[u] ){
                val[v] = max( val[v] , now ) ;
            } else val[v] = min( val[v] , now + delta ) ;
            deg[v] -- ;
            if( !deg[v] ) que[++ba] = v ;
        }
    } for( int i = 1 ; i <= M ; i ++ ) hig[i] = N ;
    pair<long long,long long> t = getHash() ;
    printf( "%d" , val[ hs.find( t.first , t.second ) ] ) ;

}

int main(){
    freopen( "chess.in" , "r" , stdin ) ;
    freopen( "chess.out", "w" , stdout) ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= N ; i ++ )
        for( int j = 1 ; j <= M ; j ++ )
            scanf( "%d" , &A[i][j] ) , All += A[i][j] ;
    for( int i = 1 ; i <= N ; i ++ )
        for( int j = 1 ; j <= M ; j ++ ){
            scanf( "%d" , &B[i][j] ) ;
            mp[i][j] = - ( A[i][j] + B[i][j] ) ;
        }
    preWork() ; solve() ;
}

T2

这道题me在考试的时候只得到了60分,因为没有发现数字重复的时候,贪心有问题= =
(虽然me拍过,然而完全随机,基本是拍不出这样的数据的

下面说一说做法吧!
根据这个大小关系限制,我们可以画出一棵树, ik ⌊ i k ⌋ i i 的父亲,0是整棵树的根

我们先从构造一种合法方案的角度出发,来寻求最有解

一种显然的合法构造方式就是:把所有的 d[] d [ ] 按照从大到小排序,然后从 1 1 n依次分配
然后我们发现,我们明显可以给 1 1 的子树分配最大的siz[1]个数,然后再给 2 2 的子树分配剩下的数中,最大的siz[2]个数( siz[u] s i z [ u ] 表示子树大小),然后这样一直分配下去,可以证明是最优的

然而在有重复数字的情况下,上面的贪心方法会出问题,比如这组数据: d[4]=1,1,1,2 d [ 4 ] = 1 , 1 , 1 , 2
最优解是 1,1,2,1 1 , 1 , 2 , 1 ,然而如果按照上面那种方法,是 1,1,1,2 1 , 1 , 1 , 2

所以我们逐个的去分配数字,来保证最优!
我们还是把数字从大到小排序,如果说分配过程中,当前分的数字有重复出现,那么就选最靠后的那一个分配出去。因为这样的话,就可以让前面重复的数字区填充这个子树,从而尽可能的节省出较大的那一些数字

具体的,me来描述一遍这个过程:首先给 1 1 分配,然后给1这个子树预留 siz[1] s i z [ 1 ] 个数。然后给 2 2 分配,在保证前面的数字预留的情况下,给2分配符合条件的最大的数字,然后又给 2 2 这个子树预留siz[2]个数…直至所有全部分配完毕。注意如果处理到了一个节点的子节点,就需要把这个预留消除(这些预留的需要空出来,才能让子节点选择)
核心思想就是,每次选择,基于保证前面合法

这个预留呢,可以用线段树实现。线段树的每个叶节点表示:这个位置往前需要预留多少
然后比如给 i i 分配一个下标为pos的数字,对应的就是 [pos,N] [ p o s , N ] 区间加子树大小 siz[i] s i z [ i ]
然后去掉预留,就是区间减 siz[i]1 s i z [ i ] − 1 (因为当前节点已经分配了一个数字)

可以看代码再理解一下

下面是代码

#include <map>
#include <functional>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

map<int,int> smap ;
int N , dg[500005] , siz[500005] , st[500005] , cnt[500005] , fa[500005] ;
double K ;
struct Node{
    Node *ch[2] ;
    int mn , flag , lf , rg ;
    void add( int delta ){
        lf += delta , rg += delta ;
        flag += delta , mn += delta ;
    }
    void update(){
        lf = ch[0]->lf ;
        rg = ch[1]->rg ;
        mn = min( ch[0]->mn , ch[1]->mn ) ;
    }
    void pushdown(){
        if( !flag ) return ;
        ch[0]->add( flag ) , ch[1]->add( flag ) ;
        flag = 0 ;
    }
} *root ;

void newNode( Node *&nd ){
    nd = new Node() ;
    nd->ch[0] = nd->ch[1] = NULL ;
    nd->flag = 0 ;
}

Node *build( int lf , int rg ){
    Node *nd ; newNode( nd ) ;
    if( lf == rg ){ nd->mn = nd->lf = nd->rg = lf ; return nd ; }
    int mid = ( lf + rg ) >> 1 ;
    nd->ch[0] = build( lf , mid ) ;
    nd->ch[1] = build( mid+1,rg ) ;
    nd->update() ; return nd ;
}

void preWork(){
    sort( dg + 1 , dg + N + 1 , greater<int>() ) ;
    for( int i = 1 , tmp = 0 ; i <= N ; i ++ ){
        if( dg[i] != dg[i-1] ) smap[ dg[i] ] = ++tmp , st[tmp] = i ;
        cnt[ tmp ] ++ ;
    } for( int i = N ; i ; i -- ){
        siz[i] ++ ;
        fa[i] = int(1.0*i/K+1e-8) ;
        siz[ fa[i] ] += siz[i] ;
    } root = build( 1 , N ) ;
}

void Modify( Node *nd , int lf , int rg , int L , int R , int delta ){
    if( L <= lf && rg <= R ){ nd->add( delta ) ; return ; }
    int mid = ( lf + rg ) >> 1 ;
    nd->pushdown() ;
    if( L <= mid ) Modify( nd->ch[0] , lf , mid , L , R , delta ) ;
    if( R >  mid ) Modify( nd->ch[1] , mid+1,rg , L , R , delta ) ;
    nd->update() ;
}

int Query( Node *nd , int lf , int rg , int limit ){
    if( lf == rg ) return lf ;
    int mid = ( lf + rg ) >> 1 ;
    nd->pushdown() ;
    if( nd->ch[1]->mn >= limit && nd->ch[0]->rg >= limit )
        return Query( nd->ch[0] , lf , mid , limit ) ;
    return Query( nd->ch[1] , mid+1,rg , limit ) ;
}

int pos[500005] ;
void solve(){
    for( int i = 1 ; i <= N ; i ++ ){
        if( fa[i] && fa[i] != fa[i-1] )
            Modify( root , 1 , N , pos[ fa[i] ] , N , siz[ fa[i] ] - 1 ) ;
        int t = Query( root , 1 , N , siz[i] ) , id = smap[ dg[t] ] ;
        pos[i] = t = st[id] + cnt[id] - 1 ;
        cnt[id] -- ;
        printf( "%d " , dg[t] ) ;
        Modify( root , 1 , N , t , N , - siz[i] ) ;
    }
}

int main(){
    scanf( "%d%lf" , &N , &K ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%d" , &dg[i] ) ;
    preWork() ; solve() ;
}

T3

T3呢,如果统计每一个连通块,然后计算贡献,无疑是很不好做的(因为基本上没有什么算法支持「连通块统计」)
所以我们变换一下,统计每个点对答案的贡献

考虑把所有点按照权值从大到小排序,然后依次把树里的点「点亮」
然后每「点亮」一个点的同时,就从这个点出发,做一遍树形 dp d p dp[i][j] d p [ i ] [ j ] 表示,在 i i 的子树里选择了j个「被点亮的点」的方案数。这个 dp d p n2 n 2 的,因为每一对点只会在LCA处才会被统计到。
另外我们可以严格的限制 dp d p 的上下界,以及忽略无效状态,这样下来的话,一次的复杂度远不到 n2 n 2

然后就按照这个步骤,一个一个的统计
复杂度上界是 (nk)n2 ( n − k ) n 2 ,这个复杂度仍然很大,因为 n,k n , k 都是 1666 1666 的范围,所以还需要进一步优化!

考虑暴力踩标程,进行一波常数优化hhhhh
然后惊奇的发现,它居然过了!!!???

下面是代码

#include <ctime>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

const int P = 64123 ;
int N , K , W , ans , head[2005] , tp ;
struct Path{
    int pre , to ;
}p[40005] ;
struct Data{
    int id , dg ;
    bool operator < ( const Data &A ) const {
        return dg > A.dg ;
    }
}d[2005] ;

void In( int t1 , int t2 ){
    p[++tp] = ( Path ){ head[t1] , t2 } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 } ; head[t2] = tp ;
}

bool lig[2005] ;
unsigned dp[2005][2005] ;
int siz[2005] ;
void dfs( int u , int f ){
    siz[u] = lig[u] ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        dfs( v , u ) ; siz[u] += siz[v] ;
    } for( int i = siz[u] ; i >= 0 ; i -- ) dp[u][i] = 0 ;

    dp[u][0] = 1 ;
    for( int i = head[u] , tmp = 0 ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        for( int j = tmp + siz[v] ; j >= 0 ; j -- ){
            unsigned sum = 0 ;
            for( int k = max( j - siz[v] , 0 ) ; k <= tmp && k <= j ; k ++ )
                sum += dp[u][k] * dp[v][j-k] %P ;
            dp[u][j] = sum %P ;
        } tmp += siz[v] ;
    } if( lig[u] ){
        for( int i = siz[u] ; i ; i -- ) 
            dp[u][i] = dp[u][i-1] ;
        dp[u][0] = 0 ;
    } dp[u][0] ++ ;
}

void solve(){
    sort( d + 1 , d + N + 1 ) ;
    for( int i = 1 ; i < K ; i ++ )
        lig[ d[i].id ] = true ;
    for( int i = K ; i <= N ; i ++ ){
        int u = d[i].id ;
        dfs( u , u ) ;
        ans += dp[u][K-1] * d[i].dg %P ;
        lig[u] = true ;
    } printf( "%d" , ans %P ) ;
}

int main(){
    int a = clock() ;
    scanf( "%d%d%d" , &N , &K , &W ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%d" , &d[i].dg ) , d[i].id = i ;
    for( int i = 1 , u , v ; i < N ; i ++ ){
        scanf( "%d%d" , &u , &v ) ;
        In( u , v ) ;
    } solve() ;
    printf( "\n%d" , clock() - a ) ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 黄河九省地图数据Shp是一种包含九个省份的地理信息系统文件格式,可以在各种GIS软件中使用。这些九个省份包括青海、甘肃、宁夏、内蒙古、陕西、山西、河南、山东和河北。该地图数据Shp覆盖了全部黄河流域,以黄河为中心,包括了黄河流域内的山脉、河流、湖泊、城市、道路等地理要素。使用这个地图数据Shp可以提供各种黄河流域的地理信息分析与处理,如水文、水资源、水环境、地质灾害、城市规划、交通等领域。 该地图数据Shp由多个文件组成,其中包括.shp、.shx、.dbf等几个文件。其中.shp文件存储了地图数据几何图形信息,.shx文件存储了几何图形的索引信息,.dbf文件存储了属性数据信息。可以使用各种GIS软件打开这些文件,可以查看和编辑地图数据,进行地理信息分析和处理。 使用黄河九省地图数据Shp 可以进行各种有关黄河流域的地理信息分析和处理。例如,分析黄河流域内不同地区的地形、气候、土壤、地质等因素对河流水质、水量、水文周期以及水文气候等方面的影响。此外,还可以计算不同区域内的水资源量, 分析地区可能面临的水资源风险, 同时可以对水资源进行合理规划与管理。 总之,黄河九省地图数据Shp为黄河流域日常采集和分析水文资料提供了有力支持, 同时也可以支持城市建设和工程规划等方面的分析。因此,黄河九省地图数据Shp在黄河流域及周边地区的管理和规划等领域中具有广泛的应用前景。 ### 回答2: 黄河九省地图数据shp是一种包含有关中国黄河流域九个省份地理信息的数字地图文件。这些省份包括:青海、甘肃、宁夏、内蒙古、陕西、山西、河南、山东和河北。该地图文件将这些省份的边界和地理要素转换为几何图形,并将其存储在shp文件中,可实现对其进行空间分析和地图制作。 这个地图数据shp文件实用性很高,可以广泛应用于黄河流域相关的地理信息领域。例如,在自然资源管理方面,可以使用它来进行土地资源、水资源、气候资源和矿产资源的分析,以便有效管理和利用这些资源。而在交通运输、城市规划和环境保护方面,该文件可以用于路线规划、区域规划和环境监测,提高规划决策的准确性和精度。 此外,该地图数据shp文件可以应用在教育和科研领域,如地理信息系统、地理编码和遥感分析等方面,为学术研究和教学活动提供相关的数据支持和参考。总之,黄河九省地图数据shp文件是非常有价值的地理信息资源,对于研究和应用黄河流域相关的地理信息问题非常有帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值