题目地址: 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;
}