冲刺国赛模拟 33
降智,同时看了半个上午的《查拉图斯特拉如是说》。有点晕。
喜报:又垫了!
染色
六个人写了三个写法。我是看着这道题就感觉是个点分树板子就冲了个点分树。点分树上维护子树内黑点到根的距离和和有多少个黑点,暴力跳加贡献。嘉然是题解做法树剖线段树。
三个人写了操作分块。但是好像并没有比点分树和树剖短。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#define int long long
using namespace std;
int n,m;
struct node{
int v,w,next;
}edge[200010];
int t,head[100010];
void add(int u,int v,int w){
edge[++t].v=v;edge[t].w=w;edge[t].next=head[u];head[u]=t;
}
int rt,sum,size[100010],mx[100010],fa[100010];
int num,dfn[100010],dep[100010],F[100010],top[100010],son[100010],Dis[100010];
bool vis[100010];
void dfs1(int x,int f){
size[x]=1;F[x]=f;dep[x]=dep[f]+1;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
Dis[edge[i].v]=Dis[x]+edge[i].w;
dfs1(edge[i].v,x);
size[x]+=size[edge[i].v];
if(size[son[x]]<size[edge[i].v])son[x]=edge[i].v;
}
}
}
void dfs2(int x,int f,int tp){
dfn[x]=++num;top[x]=tp;
if(son[x])dfs2(son[x],x,tp);
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=son[x]&&edge[i].v!=f)dfs2(edge[i].v,x,edge[i].v);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=F[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
int dis(int x,int y){
return Dis[x]+Dis[y]-2*Dis[lca(x,y)];
}
void findrt(int x,int f){
size[x]=1;mx[x]=0;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f&&!vis[edge[i].v]){
findrt(edge[i].v,x);
size[x]+=size[edge[i].v];
mx[x]=max(mx[x],size[edge[i].v]);
}
}
mx[x]=max(mx[x],sum-size[x]);
if(mx[x]<mx[rt])rt=x;
}
void div(int x){
findrt(x,0);
vis[x]=true;
for(int i=head[x];i;i=edge[i].next){
if(!vis[edge[i].v]){
sum=size[edge[i].v];rt=0;
findrt(edge[i].v,x);
fa[rt]=x;div(rt);
}
}
}
int val[100010][2],cnt[100010][2];
void update(int x){
int ret=x;
while(x)val[x][0]+=dis(x,ret),cnt[x][0]++,x=fa[x];
x=ret;
while(fa[x])val[x][1]+=dis(fa[x],ret),cnt[x][1]++,x=fa[x];
}
int query(int x){
int ans=val[x][0],ret=x;
while(fa[x]){
ans+=val[fa[x]][0]-val[x][1]+dis(ret,fa[x])*(cnt[fa[x]][0]-cnt[x][1]);
x=fa[x];
}
return ans;
}
int lastans;
signed main(){
scanf("%lld%lld",&n,&m);sum=mx[0]=n;
for(int i=2;i<=n;i++)scanf("%lld",&fa[i]),fa[i]++;
for(int i=2;i<=n;i++){
int d;scanf("%lld",&d);
add(fa[i],i,d);add(i,fa[i],d);fa[i]=0;
}
dfs1(1,0);dfs2(1,0,1);
findrt(1,0);findrt(rt,0);
div(rt);
for(int i=1;i<=n;i++)vis[i]=false;
while(m--){
int od,x;scanf("%lld%lld",&od,&x);x++;
if(od==1){
if(!vis[x])update(x),vis[x]=true;
}
else printf("%lld\n",query(x));
}
return 0;
}
寻宝游戏
咋全都会这题。
观察到它在多边形内的定义是光线投射,于是设出状态 \(dp_{x,y,s}\) 为走到 \((x,y)\),每个点引出的射线(这个只需要向一个方向引出斜率几乎为 \(0\) 的射线即可保证和任何端点不交)经过多边形的边次数奇偶性的状态为 \(s\) 的最短路程,转移可以直接 bfs。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
using namespace std;
const int dx[]={0,1,-1,0},dy[]={1,0,0,-1};
int n,m,cnt,val[10],x[10],y[10],dp[30][30][1<<8],sum[1<<8];
char s[30][30];
struct node{
int x,y,s;
};
queue<node>q;
bool check(int x,int y){
return x>=1&&x<=n&&y>=1&&y<=m&&s[x][y]!='#'&&!isdigit(s[x][y])&&s[x][y]!='B';
}
bool get(int id,int a,int b,int xx,int yy){
if(a==xx)return 0;
if(xx<a&&x[id]==a&&y[id]<b)return true;
if(xx>a&&x[id]==xx&&y[id]<b)return true;
return false;
}
int main(){
scanf("%d%d",&n,&m);
int stx,sty;
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++)if(isdigit(s[i][j]))cnt++,x[s[i][j]-'0']=i,y[s[i][j]-'0']=j;
}
for(int i=1;i<=cnt;i++)scanf("%d",&val[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]=='S')stx=i,sty=j;
if(s[i][j]=='B')x[++cnt]=i,y[cnt]=j,val[cnt]=-1000000;
}
}
for(int i=1;i<=cnt;i++)sum[1<<i-1]=val[i];
for(int i=1;i<(1<<cnt);i++)sum[i]=sum[i&(i-1)]+sum[i&-i];
memset(dp,0x3f,sizeof(dp));
q.push({stx,sty,0});dp[stx][sty][0]=0;
while(!q.empty()){
int x=q.front().x,y=q.front().y,s=q.front().s;q.pop();
for(int i=0;i<4;i++){
int xx=x+dx[i],yy=y+dy[i];
if(check(xx,yy)){
int tmp=s;
for(int j=1;j<=cnt;j++)tmp^=get(j,x,y,xx,yy)<<j-1;
if(dp[xx][yy][tmp]==0x3f3f3f3f){
dp[xx][yy][tmp]=dp[x][y][s]+1;
q.push({xx,yy,tmp});
}
}
}
}
int ans=-0x3f3f3f3f;
for(int i=0;i<(1<<cnt);i++)ans=max(ans,sum[i]-dp[stx][sty][i]);
printf("%d\n",ans);
return 0;
}
点分治
瞪不下去了就看尼采去了。结果赛后看了一眼题解写了十分钟切了。消愁。
吗了赛时没切结果石头剪刀布输了还得讲题,更消愁了。我是不是猜拳没赢过。
设 \(dp_{x,i}\) 为 \(x\) 的子树的点分树中 \(x\) 深度为 \(i\) 的方案数。转移考虑合并子树:容易发现合并两棵点分树的时候只需要保证节点到根仍然是有序的。那么实际上就是把两棵点分树到根的路径归并一下,树形背包转移即可。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
const int mod=1000000007;
int n;
struct node{
int v,next;
}edge[10010];
int t,head[5010],C[5010][5010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int dp[5010][5010],size[5010];
void dfs(int x,int f){
dp[x][1]=size[x]=1;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs(edge[i].v,x);
static int tmp[5010];
for(int j=0;j<=size[x]+size[edge[i].v];j++)tmp[j]=0;
for(int j=size[x];j>=1;j--){
for(int k=size[edge[i].v];k>=0;k--){
tmp[j+k]=(tmp[j+k]+1ll*dp[x][j]*dp[edge[i].v][k]%mod*C[j+k-1][k])%mod;
}
}
size[x]+=size[edge[i].v];
for(int j=0;j<=size[x];j++)dp[x][j]=tmp[j];
}
}
for(int i=size[x];i>=0;i--)dp[x][i]=(dp[x][i]+dp[x][i+1])%mod;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
C[0][0]=1;
for(int i=1;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
dfs(1,0);
printf("%d\n",dp[1][0]);
return 0;
}
快踩