UOJ Easy Round #4 题解

A

不难发现一些性质:

  1. 在最优解中,每个叶子到根的路径上的黑点数,等于最浅的叶子结点的深度。
  2. 对于任意一个点,都满足:它到子树内任意一个叶子结点的路径上黑点数量都相同。不妨记 f ( i ) f(i) f(i) 表示 i i i 到子树内的叶子节点所经过的黑点数量。

考虑一个树的链剖分,每个点将子树内包含最浅的叶子节点的儿子作为浅儿子

于是就有一个贪心:先根据性质 1 1 1,可以先将根节点往下的浅链上所有点染黑。然后考虑浅链上一个点 x x x 的一个深儿子 y y y,根据性质 2 2 2,我们就可以确定它的 f ( y ) f(y) f(y) 值,它等于 x x x浅儿子 p p p f ( p ) f(p) f(p)

知道 f f f 值后,就可以对这个浅儿子递归求解,显然最优的做法是将它所在的浅链上,最深的 f ( p ) f(p) f(p) 个点染黑(这样影响到的叶子结点最少),然后重复上面的步骤,对下一层浅儿子进行求解。

于是就在 O ( n ) O(n) O(n) 的时间内解决了,代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010

int n;
vector<int> e[maxn];
int fa[maxn],mson[maxn],dep[maxn],mdep[maxn];
void dfs1(int x){
    
    
	mdep[x]=1e9;
	for(int y:e[x])if(y!=fa[x]){
    
    
		dep[y]=dep[x]+1;fa[y]=x;dfs1(y);
		if(mdep[y]<mdep[mson[x]])mson[x]=y;
		mdep[x]=min(mdep[x],mdep[y]);
	}
	if(e[x].size()==1)mdep[x]=dep[x];
}
int top[maxn],L[maxn];
void dfs2(int x,int tp){
    
    
	L[tp]++;top[x]=tp;
	if(mson[x])dfs2(mson[x],tp);
	for(int y:e[x])if(y!=fa[x]&&y!=mson[x])dfs2(y,y);
}
int ans=0;
void solve(int x,int num){
    
    
	ans+=num;
	for(int i=x;i;i=mson[i]){
    
    
		int p=L[top[i]]-(dep[i]-dep[top[i]]+1);
		p=min(p,num);
		for(int j:e[i])if(j!=mson[i]&&j!=fa[i])solve(j,p);
	}
}

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++){
    
    
		scanf("%d %d",&x,&y);
		e[x].push_back(y);e[y].push_back(x);
	}
	dep[1]=1;mdep[0]=1e9;dfs1(1);dfs2(1,1);
	solve(1,L[1]);printf("%d",ans);
}

B

容易发现是数位 dp \text{dp} dp,这种精细的 dp \text{dp} dp 我是最做不来的了,调了好久才能过……

定义一个最暴力的状态,把所有要用的东西加进去: f i , j , r , s , t , 0 / 1 f_{i,j,r,s,t,0/1} fi,j,r,s,t,0/1 表示第 i i i 位为 j j j,前 i i i 位乘 k k k 给后面的进位值为 r r r,前 i i i 位的和为 s s s x × k x\times k x×k 的前 i i i 位的和为 t t t 0 / 1 0/1 0/1 表示这个数与 n n n 的大小关系, 0 0 0 表示 x > n x>n x>n 1 1 1 表示 x ≤ n x\leq n xn

转移的时候枚举第 i + 1 i+1 i+1 位放什么数即可,很容易计算各维的值。

然后这个 19 × 10 × 1000 × 340 × 340 × 2 19\times 10\times1000\times 340\times 340\times 2 19×10×1000×340×340×2 的状态数显然会爆炸,考虑优化一下。

发现我们只关心 s s s t t t 的差值,所以这两维可以缩成一维,直接记录 s − t s-t st 的值。

写完代码后发现, j j j 是没有用的,去掉即可。

然后 19 × 1000 × 340 × 2 19\times 1000\times 340\times 2 19×1000×340×2 就可以过啦!代码如下:

#include <cstdio>
#include <assert.h>
#include <algorithm>
using namespace std;
#define ll long long
#define off 200

ll n;int k;
ll f[20][1000][410][2];
//f[i][r][d][0/1]表示前i位,往前进位进了r,x-x*k等于d,此时与n的前i位的大小关系
int c[20],cn=0;
ll ans=0;

int main()
{
    
    
	scanf("%lld %d",&n,&k);
	while(n>0)c[++cn]=n%10,n/=10;
	for(int i=0;i<=9;i++)
		f[1][i*k/10][i-i*k%10+off][i<=c[1]]++;
	for(int i=2;i<=cn;i++){
    
    
		for(int j=0;j<=9;j++){
    
    
			for(int r=0;r<1000;r++){
    
    //枚举上一位给过来的进位值
				for(int d=0;d<=2*off;d++){
    
    //枚举上一位时的差值
					int R=r%10+j*k%10,G=r/10+j*k/10;
					if(R>9)R-=10,G++;
					int D=d+j-R;
					assert(G<1000);
					if(D<0||D>2*off)continue;
					if(j!=c[i]){
    
    //大小关系改变
						f[i][G][D][j<c[i]]+=f[i-1][r][d][0]+f[i-1][r][d][1];
					}else{
    
    //大小关系不变
						f[i][G][D][0]+=f[i-1][r][d][0];
						f[i][G][D][1]+=f[i-1][r][d][1];
					}
				}
			}
		}
	}
	for(int r=0;r<900;r++){
    
    
		int p=r/100+r/10%10+r%10;
		ans+=f[cn][r][p+off][1];
	}
	printf("%lld",ans-1);
}

C

做法十分巧妙(别问,问就是蒟蒻不会)。

先不管每个位上是 1 1 1 还是 − 1 -1 1,注意到如果进来了 k k k 个球,那么一定会从下面出去 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor 2k 个,从右边出去 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor 2k 个,当 k k k 是奇数时,会有 1 1 1 个球的出去位置与这个位置的值相关。

事实上我们并不关心那出去的 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor 2k 个球是谁,所以递推一下,大部分球的路径就确定下来了,至多只会有 n m nm nm 个球的路径与每个位置的值相关。

然后考虑轮廓线 dp \text{dp} dp,从上往下,从左往右推,记录下轮廓线上每个位置往下推的球数,以及从右边界出去的,并且落到篮子里的球数,每次枚举关键位置是 1 1 1 还是 − 1 -1 1 转移一下就好了。

据说状态数虽然看起来很多,实际上每个位置不超过 2.7 × 1 0 5 2.7\times 10^5 2.7×105 个,所以大概是能AC的。
在这里插入图片描述
代码如下:

#include <cstdio>
#include <cstring>
#include <assert.h>
#include <algorithm>
using namespace std;
#define ll long long
#define mod 998244353

int n,m;ll K;
char sc[20],sl[20];
ll ball[12][12];
const int base=51,hash_val=9904013;
int cur=0,tot[2],last[hash_val+10],pd[hash_val+10],npd=0;
struct par{
    
    
	int num,ans;
	long long Sta;
}sta[2][10000010];
int ne[10000010];
void add(int &x,int y){
    
    x=(x+y>=mod?x+y-mod:x+y);}
void add_sta(long long x,int y,int z){
    
    
	int tmp=(x+(((ll)y)<<7))%hash_val;
	if(pd[tmp]!=npd)pd[tmp]=npd,last[tmp]=0;
	for(int i=last[tmp];i;i=ne[i]){
    
    
		if(sta[cur][i].Sta==x&&sta[cur][i].num==y){
    
    
			return add(sta[cur][i].ans,z);
		}
	}
	ne[++tot[cur]]=last[tmp];last[tmp]=tot[cur];
	sta[cur][tot[cur]]=(par){
    
    y,z,x};
}
ll bin[12];
void trans(int x,int new_ball,par &s){
    
    
	int b1=s.Sta/bin[x]%base,b2=s.Sta/bin[x+1]%base,b3=b1+b2+new_ball;
	if(~b3&1){
    
    
		add_sta(s.Sta+((b3>>1)-b1)*bin[x]+((b3>>1)-b2)*bin[x+1],s.num,2ll*s.ans%mod);
	}else{
    
    
		ll Sta=s.Sta+((b3>>1)-b1)*bin[x]+((b3+1>>1)-b2)*bin[x+1];
		add_sta(Sta,s.num,s.ans);
		add_sta(Sta+bin[x]-bin[x+1],s.num,s.ans);
	}
}
int w[11*11];

int main()
{
    
    
	scanf("%d %d %lld",&n,&m,&K);
	scanf("%s %s",sc+1,sl+1);
	ball[1][1]=K;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
    
    
			ball[i+1][j]+=ball[i][j]>>1;
			ball[i][j+1]+=ball[i][j]>>1;
			ball[i][j]&=1;
		}
	ll pre_ball=0;
	for(int i=1;i<=n;i++)if(sc[i]=='1')pre_ball+=ball[i][m+1];
	for(int i=1;i<=m;i++)if(sl[i]=='1')pre_ball+=ball[n+1][i];
	bin[m+1]=1;for(int i=m;i>=1;i--)bin[i]=bin[i+1]*base;
	sta[0][tot[0]=1]=(par){
    
    0,1,0};
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			npd++;cur^=1;tot[cur]=0;
			for(int k=1;k<=tot[cur^1];k++)
				trans(j,ball[i][j],sta[cur^1][k]);
		}
		for(int j=1;j<=tot[cur];j++){
    
    
			if(sc[i]=='1')sta[cur][j].num+=sta[cur][j].Sta%base;
			sta[cur][j].Sta/=base;
		}
	}
	for(int i=1;i<=tot[cur];i++){
    
    
		ll &s=sta[cur][i].Sta;int num=sta[cur][i].num;
		for(int j=m;j>=1;j--){
    
    
			if(sl[j]=='1')num+=s%base;
			s/=base;
		}
		add(w[num+1],sta[cur][i].ans);
	}
	for(int i=2;i<=n*m+1;i++)add(w[i],w[i-1]);
	int q;scanf("%d",&q);while(q--){
    
    
		ll l,r;scanf("%lld %lld",&l,&r);
		if(r<pre_ball||l>pre_ball+n*m){
    
    
			puts("0");continue;
		}
		l=max(1ll,l-pre_ball+1);r=min(1ll*n*m+1,r-pre_ball+1);
		printf("%d\n",(w[r]-w[l-1]+mod)%mod);
	}
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109784859