说在前面
模拟考,考了260巨开心hhhhh
T3正解是什么鬼啊QAQ…….当然是暴力出奇迹啊!
题目
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
的父亲,是整棵树的根
我们先从构造一种合法方案的角度出发,来寻求最有解
一种显然的合法构造方式就是:把所有的
d[]
d
[
]
按照从大到小排序,然后从
1
1
到依次分配
然后我们发现,我们明显可以给
1
1
的子树分配最大的个数,然后再给
2
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
分配,然后给这个子树预留
siz[1]
s
i
z
[
1
]
个数。然后给
2
2
分配,在保证前面的数字预留的情况下,给分配符合条件的最大的数字,然后又给
2
2
这个子树预留个数…直至所有全部分配完毕。注意如果处理到了一个节点的子节点,就需要把这个预留消除(这些预留的需要空出来,才能让子节点选择)
核心思想就是,每次选择,基于保证前面合法
这个预留呢,可以用线段树实现。线段树的每个叶节点表示:这个位置往前需要预留多少
然后比如给
i
i
分配一个下标为的数字,对应的就是
[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
的子树里选择了个「被点亮的点」的方案数。这个
dp
d
p
是
n2
n
2
的,因为每一对点只会在LCA处才会被统计到。
另外我们可以严格的限制
dp
d
p
的上下界,以及忽略无效状态,这样下来的话,一次的复杂度远不到
n2
n
2
然后就按照这个步骤,一个一个的统计
复杂度上界是
(n−k)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 ) ;
}