HDU 4351 - Digital root

15 篇文章 0 订阅

 

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=4351

 

这题囧死了~~~    耽搁了几天,终于解决它了~~

 

0的字根是0。

 

非0数a的字根是 a%9==0 ? a%9 : 0  。

 

=========================================================================================================

 

方法一: 电科大牛的思路,揣测了好几天~~~

 

sum[i] 表示 前i个之和的字根

 

然后进行预处理:

                              l[i][j] 表示,从i位置向右延伸,在sum中第一次出现j字根时的坐标。

                              r[i][j] 表示,从i位置向左延伸,在sum中第一次出现j字根时的坐标。  

 

于是, 每当求(L,R)之间的字根时,我们直接暴力枚举(O(100) 的复杂度)所有可能。从字根9开始,遍历到0。每遍历到一个数时,再枚举组成这个数的所有可能。

 

只需要满足 l[L-1][x] < r[R][y] ,并且 x+9 的结果字根为 y。 那么就表示(L,R)内含有区间,其区间内所有数之和的字根为9 。

 

当然因为有0的情况,所以判断 l[L-1][x] < r[R][y] 时,还用cnt特殊处理(cnt 忽略0 记录数的下标,即如果遇到0,其下标为之前的第一非0数的下标)。

 

zero判断区间内是否含有0。

 

#include<iostream>
#include<cstdio>
#define f(x) ((x)==0?0:((x)%9==0?9:(x)%9))

using namespace std;

const int maxn = 101000;

int sum[maxn],zero[maxn],cnt[maxn];
int l[maxn][10],r[maxn][10];
int res[6];

int main(){
	int t,tt;
	int n,m,i,j,a,b,x,y,num,k;
	scanf("%d",&t);
	for(tt=1;tt<=t;tt++){
		printf("Case #%d:\n",tt);
		scanf("%d",&n);
		sum[0]=cnt[0]=zero[0]=0;
		for(i=1;i<=n;i++){
			scanf("%d",&a);
			cnt[i]=a?i:cnt[i-1];
			zero[i]=a?zero[i-1]:i;
			sum[i]=f(sum[i-1]+a);
		}
		cnt[i]=cnt[i-1]+1;
		for(i=0;i<10;i++) l[n+1][i]=n+1,r[0][i]=0;
		for(i=n;i>=0;i--)
			for(j=0;j<10;j++)
				l[i][j]=sum[i]==j?i:l[i+1][j]; 
		for(i=1;i<=n;i++)
			for(j=0;j<10;j++)
				r[i][j]=sum[i]==j?i:r[i-1][j];
		scanf("%d",&m);
		while(m--){
			scanf("%d%d",&a,&b);
			for(k=9,num=5;k>0 && num>0;k--){
				for(i=0;i<=9;i++){
					x=i;
					y=i+k;
					if(y>9) y-=9;
					if(cnt[l[a-1][x]]<cnt[r[b][y]]) break;                
				}
				if(i<=9) res[--num]=k;
			}
			if(num>0) if(zero[b]>=a) res[--num]=0;
			while(num>0) res[--num]=-1;
			for(num=5;num--;)
				printf("%d%c",res[num],num?' ':'\n');
		}
		if(tt<t) puts("");
	}
	return 0;
}


 

=========================================================================================================

 

方法2:

                  线段树: 区间合并、区间查询

                  用二进制数存储0~9每个字根的存在状态。

                  那么此时 a|b 表示对两个区间的所有字根进行合并, a*b 表示两个区间所能组成的字根。

                  线段树变量存储字根状态,主要有4种区间:以左端点开始的区间、以右端点开始的区间、整个区间、所有子区间(即所有可能字根)。

 

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll (v<<1)
#define rr (v<<1|1)
#define tmid (l+r>>1)
#define f(x) ((x)==0?0:((x)%9==0?9:(x)%9))
#define d(x) ((x)>9?(x)-9:(x))

using namespace std;

const int maxn = 100100;

int tot[maxn<<2];  // 保存区间内所有的数(从左端点到右端点)能够组成的字根 
int prt[maxn<<2];  // 保存所有子区间能够组成的字根 
int ls[maxn<<2];  // 保存以本区间左端点为左端点 的所有子区间 能够组成的字根 
int rs[maxn<<2];  // 保存以本区间右端点为右端点 的所有子区间 能够组成的字根 
int mul[1025][1025];
int s[maxn];

void init_(){
	for(int x=0;x<=1023;x++)
		for(int y=x;y<=1023;y++){
			mul[x][y]=0;
			for(int i=0;i<10;i++)
				if((1<<i)&x)
					for(int j=0;j<10;j++)
						if((1<<j)&y)
							mul[x][y]|=1<<d(i+j);
			mul[y][x]=mul[x][y];
		}
}

struct Result{
	int prt,tot,ls,rs;
	Result(){}
	Result(int p,int t,int l,int r){prt=p,tot=t,ls=l,rs=r;}
}res;

Result query(int L,int R,int l,int r,int v){
	if(L<=l && r<=R)
		return Result(prt[v],tot[v],ls[v],rs[v]);
	if(R<=tmid)
		return query(L,R,l,tmid,ll);
	if(L>tmid)
		return query(L,R,tmid+1,r,rr);
	Result q,ql,qr; 
	ql=query(L,R,l,tmid,ll);
	qr=query(L,R,tmid+1,r,rr);
	q.tot=mul[ql.tot][qr.tot];
	q.ls=ql.ls|mul[ql.tot][qr.ls];
	q.rs=qr.rs|mul[qr.tot][ql.rs];
	q.prt=ql.prt|qr.prt|mul[ql.rs][qr.ls];
	return q;
}

void make_tree(int l,int r,int v){
	if(l==r){
		prt[v]=tot[v]=ls[v]=rs[v]=1<<f(s[l]);
		return;
	}
	make_tree(l,tmid,ll);
	make_tree(tmid+1,r,rr);
	tot[v]=mul[tot[ll]][tot[rr]];
	ls[v]=ls[ll]|mul[tot[ll]][ls[rr]];
	rs[v]=rs[rr]|mul[tot[rr]][rs[ll]];
	prt[v]=prt[ll]|prt[rr]|mul[rs[ll]][ls[rr]];
}

int main(){
	int t,tt;
	int m,n,i,j,L,R,num,ans[5];
	init_();
	scanf("%d",&t);
	for(tt=1;tt<=t;tt++){
		scanf("%d",&n);
		for(i=1;i<=n;i++)
			scanf("%d",&s[i]);
		make_tree(1,n,1);
		printf("Case #%d:\n",tt);
		scanf("%d",&m);
		while(m--){
			scanf("%d%d",&L,&R);
			res=query(L,R,1,n,1);
			num=0;
			for(i=9;i>=0 && num<5;i--)
				if(res.prt&(1<<i))
					ans[num++]=i;
			while(num<5) ans[num++]=-1;
			for(i=0;i<num;i++) printf(i?" %d":"%d",ans[i]);
			puts("");
		}
		if(tt<t) puts("");
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值