T1 一双木棋chess
n,m<=10
Alice想最大化差值,Bob想最小化差值。发现棋子呈阶梯状排布,总状态数阶梯状排布,阶乘复杂度。
哈希记录状态后爆搜,记录当前是谁的回合,按照自身的决策去最大化/最小化价值。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int base=15;
const ll INF=2e18;
map<ll,int>ma;
int st[15],A[15][15],B[15][15],n,m;
ll range;
ll gethash(){
ll ans=0;
for(int i=1;i<=n;i++)ans=ans*base+st[i];
return ans;
}
ll dfs(ll S,int ty){
if(ma[S])return ma[S];
if(S==range)return 0;
ll ans=ty==0?-INF:INF;
for(int i=1;i<=n;i++){
if(st[i]<st[i-1]){
st[i]++;
if(ty==0)ans=max(ans,dfs(gethash(),ty^1)+A[i][st[i]]);
else ans=min(ans,dfs(gethash(),ty^1)-B[i][st[i]]);
st[i]--;
}
}
return ma[S]=ans;
}
int main(){
// freopen("chess.in","r",stdin);
// freopen("chess.out","w",stdout);
memset(st,0,sizeof(st));
scanf("%d%d",&n,&m);st[0]=m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&A[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&B[i][j]);
}
}
range=0;
for(int i=1;i<=n;i++)range=range*base+m;
cout<<dfs(0,0);
}
T2.IIIDX
读题题意即 有一些形态相近的树组成的森林,树的形态给定,每个节点从给定权值中赋予一个权值,使得每棵树都是一个小根堆,并且要求字典序最大的方案。
把每棵树根连向0号节点形成一棵树。很直接的思路,排序后,对于同层次的树,右边的填小的留出位置给左边。左边填可控区间内最小的。
但是题目中有一个很奇怪的条件就是,数值可能会相同。。遇到奇怪的条件想一下反例。发现这样做在有相同时是合法的而不是最优的。
我们考虑一个排好序的序列,从中找出一个点填入,使这个点的值尽量大。提前就要预留好他子树中的位置,在有位置的情况下,找最左边的(从大到小排序)。然后他右边的节点都少了这些点的位置。对于一堆权值相同的节点,为了保证正确性,我们取这里面最右边的节点。
这个操作是可以用线段树实现的。
对于他父亲已经预留了位置,在查询第一个孩子时要把这个操作还原回去。
线段树叶子记录每个节点左边包括自身还剩几个空节点。这个具有单调性。
取一个min在线段树上二分就可以得到答案、
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
struct edge{
int to,next;
}e[MAXN<<1];
int head[MAXN],cnt=0;
inline void add(int u,int v){e[++cnt]=(edge){v,head[u]},head[u]=cnt;}
inline int read(){
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
bool cmp(int a,int b){
return a>b;
}
int size[MAXN],d[MAXN],after[MAXN],fa[MAXN],output[MAXN],n;
double kk;
void dfs(int u){
size[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
dfs(v);
size[u]+=size[v];
}
}
struct xds{
#define lson (o<<1)
#define rson (o<<1|1)
int minv[MAXN<<2],lzt[MAXN<<2];
inline void pushup(int o){
minv[o]=min(minv[lson],minv[rson]);
}
inline void pushdown(int o){
if(lzt[o]){
lzt[lson]+=lzt[o];lzt[rson]+=lzt[o];
minv[lson]+=lzt[o];minv[rson]+=lzt[o];
lzt[o]=0;
}
}
inline void build(int o,int l,int r){
if(l==r){minv[o]=l;return;}
int mid=l+r>>1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(o);
}
inline void change(int o,int l,int r,int ql,int qr,int val){
if(ql<=l&&qr>=r){minv[o]+=val;lzt[o]+=val;return;}
int mid=l+r>>1;
pushdown(o);
if(ql<=mid)change(lson,l,mid,ql,qr,val);
if(qr>=mid)change(rson,mid+1,r,ql,qr,val);
pushup(o);
}
inline int find(int o,int l,int r,int k){
if(l==r){return minv[o]>=k?l:l+1;}
pushdown(o);
int mid=l+r>>1;
if(k>minv[rson])return find(rson,mid+1,r,k);
return find(lson,l,mid,k);
}
#undef lson
#undef rson
}T;
int main(){
// freopen("iiidx.in","r",stdin);
// freopen("iiidx.out","w",stdout);
scanf("%d%lf",&n,&kk);
for(int i=1;i<=n;i++){
d[i]=read();
}
for(int i=n;i>=1;i--){
int p=int(i/kk);
fa[i]=p;
add(p,i);
}
sort(d+1,d+1+n,cmp);
for(int i=n;i>=1;i--)if(d[i]==d[i+1])after[i]=after[i+1]+1; else after[i]=0;
T.build(1,1,n);
dfs(0);
// for(int i=1;i<=n;i++)cout<<fa[i]<<" "<<size[i]<<" "<<after[i]<<endl;
for(int i=1;i<=n;i++){
if(fa[i]&&fa[i]!=fa[i-1])T.change(1,1,n,output[fa[i]],n,size[fa[i]]-1);
int p=T.find(1,1,n,size[i]);
p+=after[p];after[p]++;p-=after[p]-1;
output[i]=p;
T.change(1,1,n,p,n,-size[i]);
}
for(int i=1;i<=n;i++)printf("%d ",d[output[i]]);
puts("");
return 0;
}
T3 秘密袭击coat
标算不会,有一种优美的暴力。
题意为 给一棵树,从中选出所有不相同的元素个数大于k的连通块,求这些连通块中第k大元素权值和。
答案对64123取模。
我们考虑分别计算每个点的答案。
如果这个点在块中为第k大的连通块有v个。对答案的贡献为vs。
问题转化为求出每个点是第k大元素的连通块个数。
枚举每个点作为根。
如果这个点rank<k 可以直接剪掉。
否则统计答案。
因为连通块的信息是跨子树的,所以我们不能像通常一下子树上传。还要从父节点下传信息,再子树更新信息。
如果下传时他碰到了一个权值比他大的节点v,v与root所在连通块中,root为第k大的方案数等于 root与v的father连通块中,root为第k-1大的方案数。若v权值比root小则为k大的方案数。
子节点信息上传更新父节点计算答案即可。这样在点的权值均不相同时显然是对的。
若点的权值相同,我们可以强行按标号顺序区分权值大小,其余做法同上。
复杂度分析:
只有 n−kn−k 个点 每个点作为根进行 dfsdfs。每次dfs遍历 nn 个点,每个点上传加下传复杂度O(k)O(k)
整体复杂度 O(n2k−nk2)O(n2k−nk2) 二次函数当 kk 取得 n/2n/2 时复杂度最差为 O(n3/2−n3/4)=O(n3/4)O(n3/2−n3/4)=O(n3/4)
因为良心出题人并没有卡,所以可以A。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=2000;
const int MOD=64123;
ll f[MAXN][MAXN];
struct edge{
int to,next;
}e[MAXN<<1];
int head[MAXN],val[MAXN],cnt=0,V,n,m,k,w;
inline void add(int u,int v){
e[++cnt]=(edge){v,head[u]},head[u]=cnt;
e[++cnt]=(edge){u,head[v]},head[v]=cnt;
}
inline ll ADD(ll x,ll y){
x+=y;if(x>=MOD)x-=MOD;
return x;
}
void dfs(int u,int fa){
if(val[u]>val[V]||(val[u]==val[V]&&u>V))for(int i=1;i<=k;i++)f[u][i]=f[fa][i-1];
else for(int i=1;i<=k;i++)f[u][i]=f[fa][i];
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
dfs(v,u);
for(int j=1;j<=n;j++)f[u][j]=ADD(f[u][j],f[v][j]);
}
}
ll ans=0;
void calc(int p){
//memset(f,0,sizeof())
int tim=1;
for(int i=1;i<=n;i++)if(val[i]>val[p]||(val[i]==val[p]&&i>p))tim++;
if(tim<k)return;
V=p;
for(int i=0;i<=n;i++)f[p][i]=0;
f[p][1]=1;
for(int i=head[p];i;i=e[i].next){
dfs(e[i].to,p);
for(int j=1;j<=n;j++)f[p][j]=ADD(f[p][j],f[e[i].to][j]);
}
//cout<<f[p][3]<<" "<<p<<endl;
ans+=val[p]*f[p][k];
}
int main(){
//freopen("coat.in","r",stdin);
//freopen("coat.out","w",stdout);
scanf("%d%d%d",&n,&k,&w);
for(int i=1;i<=n;i++){
scanf("%d",&val[i]);
}
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
for(int i=1;i<=n;i++){
calc(i);
}
cout<<ans%MOD<<endl;
return 0;
}