果然说Day1会水成狗,Day2被虐成狗,这次改题改的我都没有心情好好玩耍了……
附上题解
T1 送你一棵圣诞树
Description
给出m+1棵树,从0到m编号。T0只有一个编号为0的点。如果用Ti表示第i棵树(i>0),则在第Tai 棵树的第ci 个点和第Tbi 棵树的第di 个点之间连上了一条长度为li 的边来表示它。并且,保持Tai 中的所有节点编号不变,然后如果Tai 中有s 个节点,Tbi 中的所有节点的编号加上s。对于每棵树,求
d(i,j)表示节点i到j的最短路径。
Input
第一行输入一个正整数T 表示数据组数。每组数据的第一行是一个整数m,接下来m 行每行五个整数ai, bi, ci, di, li,保证0 <= ai, bi < i, 0<= li<= 10^9,ci, di 存在。
Output
对于每组询问输出m 行。第i 行输出Ti 的权值。答案可能很大,请对10^9 + 7 取模后输出。
Sample Input
1
2
0 0 0 0 2
1 1 0 0 4
Sample Output
2
28
Data Constraint
对于30% 的数据,m <= 8
对于60% 的数据,m <= 16
对于100% 的数据,1 <= m<= 60,T<= 100
Solution
神题一道,改了两天。
首先,我们要知道,这样建出的树可能非常大,是不能用普通的方法表示的。
用ans[i]来表示Ti的答案,cout[i]表示Ti的节点个数,all(x,i)表示Tx中所有的点到i这个点的距离和,因为这棵树由两棵树组成,我们可以推出如下公式:
ans[i]=ans[a[i]]+ans[b[i]]+cout[a[i]] * cout[b[i]] * l[i]+all(a[i],c[i]) * cout[b[i]]+all(b[i],d[i])*cout[a[i]];
然后我们思考如何求all(x,i)。发现上面我们已经把一个问题分成了许多子问题,那么我们能不能再把all分开来求呢?
很明显是可以的。设to(x,i,j)表示Tx中,点i到点j的距离,则
若l为组成x的左子树,r为右子树,
(1)i存在于l中
all(x,i)=all(r,d[x])+all(l,i)+(l[x]+to(l,i,c[x]))*cout[r];
(2)i存在于r中
all(x,i)=all(l,c[x])+all(r,i)+(l[x]+to(r,i,d[x]))*cout[l];
有了上面的经验,我们也可以吧to(x,i,j)分开来求,详细见代码。
然后,我们可以写记忆化搜索,这样每一次to的复杂度是O(n)的,每一次all都要调用一次to,所以复杂度是O(n^2)。
总复杂度为O(n^3)
Code
#include<cstdio>
#include<cstring>
#include<map>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define mo 1000000007
#define N 65
using namespace std;
map<pair<ll,ll>,ll> h[N];
map<ll,ll> h1[N];
struct note{
ll a,b,c,d,l,w;
void add() {h[w][make_pair(c,d)]=l;h[w][make_pair(d,c)]=l;}
}f[N];
ll cout[N],ans[N],c[N];
ll ty,n;
ll to(ll z,ll x,ll y) {
if (x==y) return 0;
if (h[z][make_pair(x,y)]) return h[z][make_pair(x,y)];
ll l=f[z].a,r=f[z].b,sum;
if (x<cout[l]&&y<cout[l]) sum=to(l,x,y);
else if (x<cout[l]&&y>=cout[l])
sum=(to(l,x,f[z].c)+f[z].l+to(r,y-cout[l],f[z].d))%mo;
else if (x>=cout[l]&&y<cout[l])
sum=(to(r,x-cout[l],f[z].d)+f[z].l+to(l,y,f[z].c))%mo;
else sum=to(r,x-cout[l],y-cout[l]);
h[z][make_pair(x,y)]=sum;
return sum;
}
ll all(ll x,ll y) {
if (!x) return 0;
if (h1[x][y]) return h1[x][y];
ll l=f[x].a,r=f[x].b,z;
if (y<cout[l]) z=(all(r,f[x].d)+all(l,y)+
(f[x].l+to(l,f[x].c,y))*c[r]%mo)%mo;
else z=(all(l,f[x].c)+all(r,y-cout[l])+
(f[x].l+to(r,f[x].d,y-cout[l]))*c[l]%mo)%mo;
h1[x][y]=z;
return z;
}
int main() {
for(scanf("%d",&ty);ty;ty--) {
scanf("%d",&n);
memset(cout,0,sizeof(cout));cout[0]=1;c[0]=1;
memset(ans,0,sizeof(ans));fo(i,1,n) h[i].clear(),h1[i].clear();
fo(i,1,n) {
scanf("%lld%lld%lld%lld%lld",
&f[i].a,&f[i].b,&f[i].c,&f[i].d,&f[i].l);
f[i].w=i;f[i].l=f[i].l%mo;
f[i].d=f[i].d+cout[f[i].a];
f[i].add();f[i].d=f[i].d-cout[f[i].a];
c[i]=(c[f[i].a]+c[f[i].b])%mo;
cout[i]=cout[f[i].a]+cout[f[i].b];
ans[i]=(ans[f[i].a]+ans[f[i].b]+
c[f[i].a]%mo*c[f[i].b]%mo*f[i].l%mo
+all(f[i].a,f[i].c)*c[f[i].b]%mo+
all(f[i].b,f[i].d)*c[f[i].a]%mo)%mo;
printf("%lld\n",ans[i]);
}
}
}
T2 我想大声告诉你
Description
有n个人,每一轮随机选择没有出局的人一个人出局,然后剩下的人受到一次攻击,每个人被攻击就有 p的概率淘汰,求当k=0..n-1时,每个人受到k次攻击然后出局的概率是多少,答案在模258280327 意义下。注意,出局不等于淘汰。
Input
第一行输入一个正整数T 表示数据组数。
对于每一组数据输入仅一行三个数n, x, y,表示在这组数据中有n 个人参赛,p = x/y。保证y 和258280327 互质。
Output
对于每组数据,输出一行n 个整数,表示对于k = 0到n - 1 的概率在模258280327 意义下的值。
Sample Input
2
3 40 100
9 32 1049
Sample Output
172186885 92980918 16529941
229582513 163885050 39458156 102374877 116777758 216371874 55544199 95860736 8136787
Data Constraint
对于60% 的数据,n <=100
对于100% 的数据,n <= 2* 10^3,1 <= T <= 5,0<= x < y <= 10^9
Solution
没什么好说的了,首先你要理解清楚题意,然后再看懂样例,然后你就大概会做了。
因为它是随机选人出局,我们把它变成按顺序选人出局,对于每个k,所有人出局的总概率是不变的。而题目描述的每个人出局的概率是相等的,所以按我们只需要做一次线性的dp,算出答案,然后取个平均数就行了。
Code
#include<cstdio>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define N 2005
#define mo 258280327
using namespace std;
int ty,n;
ll x,y,f[N][N],ans,p[N];
ll mi(ll x,ll y) {
if (y==0) return 1;
ll z=x;y--;
while (y) {
if (y%2==1) z=z*x%mo;
x=x*x%mo;y/=2;
}
return z;
}
int main() {
for(scanf("%d",&ty);ty;ty--) {
scanf("%d%lld%lld",&n,&x,&y);
memset(f,0,sizeof(f));f[1][0]=1;
ll ni=mi(y,mo-2);p[0]=1;
fo(i,1,n) p[i]=p[i-1]*(y-x)%mo*ni%mo;
fo(i,1,n-1)
fo(j,0,n-1)
if (f[i,j]) {
f[i+1][j]=(f[i+1][j]+f[i][j]*(1-p[j]+mo)%mo)%mo;
f[i+1][j+1]=(f[i+1][j+1]+f[i][j]*p[j+1]%mo)%mo;
}
fo(i,0,n-1) {
ll ans=0;
fo(j,1,n) ans=(ans+f[j][i])%mo;
printf("%lld ",ans*mi(n,mo-2)%mo);
}
printf("\n");
}
}
T3 对你的爱深不见底
Description
定义一些字符串,s1=’a’,s2=’b’,si=si-1+si-2,给出n,m,求sn的前m个字符形成的字符串中,最长的相等前后缀的长度。如,ababa为3。
Input
第一行输入一个正整数T 表示数据组数。
对于每组数据,第一行是两个整数n;m。保证1<= m <=|sn|
Output
对于每组数据,输出一个整数表示答案。答案可能很大,你只需要输出模258280327 后的答案。
Sample Input
2
4 3
5 5
Sample Output
1
2
Data Constraint
对于30% 的数据,n <= 20
对于60% 的数据,n <= 60
对于100% 的数据,n <= 10^3,1 <= T <= 100
Solution
首先,你得打个表,然后找规律。然后你就会发现,设k为最小的sk使得|sk|>m+1,那么答案就是m-|sk-2|,上高精度就行了。证明自行脑补,意会即可。
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 1005
#define maxn 100000000
#define mo 258280327
#define ll long long
using namespace std;
struct note{
int l,a[505];
}f[N],m,one;
note add(note x,note y) {
note z;memset(z.a,0,sizeof(z.a));z.l=max(x.l,y.l);
fo(i,1,z.l) {
z.a[i]+=x.a[i]+y.a[i];z.a[i+1]+=z.a[i]/maxn;z.a[i]%=maxn;
}
if (z.a[z.l+1]) z.l++;
return z;
}
note dec(note x,note y) {
note z;memset(z.a,0,sizeof(z.a));z.l=x.l;
fo(i,1,z.l) {
z.a[i]=x.a[i]-y.a[i];
if (z.a[i]<0) z.a[i]+=maxn,x.a[i+1]--;
}
if (!z.a[z.l]) z.l--;
return z;
}
bool big(note x,note y) {
if (x.l>y.l) return 1;
else if (x.l<y.l) return 0;
fd(i,x.l,1)
if (x.a[i]>y.a[i]) return 1;
else if (x.a[i]<y.a[i]) return 0;
return 0;
}
char s[N];
int ty,n,l,r,mid,ten[9];
ll ans;
int main() {
f[1].l=f[1].a[1]=f[2].l=f[2].a[1]=one.l=one.a[1]=1;ten[1]=1;
fo(i,2,8) ten[i]=ten[i-1]*10;ten[0]=ten[8];
fo(i,3,1003) f[i]=add(f[i-1],f[i-2]);
for(scanf("%d",&ty);ty;ty--) {
scanf("%d",&r);l=0;r+=3;m.l=0;memset(m.a,0,sizeof(m.a));
scanf("%s",s+1);int k=0,len=strlen(s+1);
fd(i,len,1) {
k=k+(s[i]-'0')*ten[(len-i+1)%8];
if ((len-i+1)%8==0) m.a[++m.l]=k,k=0;
}
if (k) m.a[++m.l]=k;
note p=add(m,one);
while (l<r) {
mid=(l+r)/2;
if (big(f[mid],p)) r=mid;else l=mid+1;
}
l-=2;
m=dec(m,f[l]);ans=0;
fd(i,m.l,1) ans=(ans*maxn%mo+m.a[i])%mo;
printf("%lld\n",ans);
}
}
果然跪了呀!整场比赛都被第二题的题意搞得不要不要的,只打了第三题的暴力。看来策略还是有问题,不要死磕一道题,尤其是那么KD的题!