文章目录
-
-
- A. Groundhog and 2-Power Representation
- B. Groundhog ans Apple Tree
- C. Groundhog ans Gmaming Time
- D. Groundhog and Golden Apple
- E. Groundhog Chasing Death
- F. Groundhog Looking Dowdy
- G. Groundhog Playing Scissors
- H. Groundhog Speaking Groundhogish
- I. The Crime-solving Plan of Groundhog
- J. The Escape Plan of Groundhog
- K.The Flee of Groundhog
-
A. Groundhog and 2-Power Representation
给出一个字符串,其中
2(x)
表示 2 2 2 的 x x x 次方, x x x 只可能是 0 0 0 或表达式,其中还可能有 + + + 表示加法,计算这个式子的值。
直接递归求解即可,能一发过样例然后调大数组就AC我是没想到的,蒟蒻觉得可能要调试很久来着……。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
char s[20010];int n;
struct GJD{
int a[310],t;
GJD(){
memset(a,0,sizeof(a));t=1;}
void update(){
for(int i=1;i<=t;i++)if(a[i]>9){
a[i+1]+=a[i]/10;
a[i]%=10;
if(i==t)t++;
}
}
int &operator [](int x){
return a[x];}
GJD operator +(GJD B){
GJD re;re.t=max(t,B.t);
for(int i=1;i<=re.t;i++)re.a[i]=a[i]+B[i];
re.update();return re;
}
GJD operator *(GJD &B){
GJD re;re.t=t+B.t-1;
for(int i=1;i<=t;i++){
for(int j=1;j<=B.t;j++)
re.a[i+j-1]+=a[i]*B[j];
}
re.update();return re;
}
void div2(){
for(int i=1;i<=t;i++){
if(i>1)a[i-1]+=a[i]&1?5:0;
a[i]/=2;
}
while(t>1&&!a[t])t--;
}
void print(){
for(int i=t;i>=1;i--)printf("%d",a[i]);printf("\n");}
};
struct par{
int endpos;GJD re;};
GJD ksm(GJD y){
GJD re;re[1]=1;
GJD x;x[1]=2;
while(y.t>1||y[1]){
if(y[1]&1)re=re*x;
x=x*x;y.div2();
}
return re;
}
par dfs(int pos){
GJD re;
for(;;pos++){
if(s[pos]==')')break;
if(s[pos]=='2'){
if(s[pos+1]=='('){
par p=dfs(pos+2);
re=re+ksm(p.re);
pos=p.endpos;
}else re[1]+=2;
}
}
re.update();
return (par){
pos,re};
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);s[++n]=')';s[n+1]=0;
par ans=dfs(1);ans.re.print();
}
B. Groundhog ans Apple Tree
有一棵苹果树,每个节点上有一个苹果,苹果可以回复HP,经过一条边会损耗HP,HP任意时刻不能小于 0 0 0,每条边只能经过两次,休息 1 s 1s 1s 可以增加 1 1 1 HP,问最少休息时间。
令 a X a_X aX 表示走完 X X X 子树后HP的变化量, b X b_X bX 表示走 X X X 的子树过程中HP的最低值,换言之, − b X -b_X −bX 也就是需要在 x x x 处通过休息回复的HP值。
令 s o n son son 为 X X X 的儿子们的一个排列,容易发现, b X = min i = 1 ∣ s o n ∣ { ( ∑ j = 1 i − 1 a s o n j ) + b s o n i } b_X=\min_{i=1}^{|son|}\{(\sum_{j=1}^{i-1}a_{son_j})+b_{son_i}\} bX=mini=1∣son∣{ (∑j=1i−1asonj)+bsoni}。
要使 b X b_X bX 尽可能大,只能通过调整访问儿子的顺序,对于两个访问顺序相邻的儿子 x , y x,y x,y,如果先访问 x x x 再访问 y y y 更优,那么一定满足:
min ( b x , a x + b y ) > min ( b y , a y + b x ) \min(b_x,a_x+b_y)>\min(b_y,a_y+b_x) min(bx,ax+by)>min(by,ay+bx)
考虑 a x , a y a_x,a_y ax,ay 符号不同时,假如 a x > 0 , a y < 0 a_x>0,a_y<0 ax>0,ay<0 那么显然成立。
再考虑他们符号相同时,若 a x , a y ≥ 0 a_x,a_y\geq 0 ax,ay≥0,通过讨论 b x b_x bx 与 a x + b y a_x+b_y ax+by 之间的大小关系,可以知道此时只有满足 b x > b y b_x>b_y bx>by 才能使上式成立。
类似的,当 a x , a y < 0 a_x,a_y<0 ax,ay<0 时,只有当 a x + b y > a y + b x a_x+b_y>a_y+b_x ax+by>ay+bx 时,即 a x − b x > a y − b y a_x-b_x>a_y-b_y ax−bx>ay−by 时才成立。
于是排个序再依次遍历就做完了,代码如下:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long long
#define pb push_back
int T,n;
int val[maxn];
vector<int> e[maxn],w[maxn];
ll a[maxn],b[maxn];
bool cmp(int x,int y){
if(a[x]>=0&&a[y]>=0)return b[x]>b[y];
else if(a[x]<0&&a[y]<0)return a[x]-b[x]>a[y]-b[y];
else return a[x]>a[y];
}
void dfs(int x,int fa){
a[x]=val[x];b[x]=0;
for(int i=0;i<e[x].size();i++)if(e[x][i]!=fa){
int y=e[x][i],z=w[x][i];
dfs(y,x);
a[y]-=2ll*z;b[y]-=z;
b[y]=min(b[y],a[y]);
}
sort(e[x].begin(),e[x].end(),cmp);
for(int y:e[x])if(y!=fa){
b[x]=min(b[x],a[x]+b[y]);
a[x]+=a[y];
}
}
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&val[i]);
e[i].clear();w[i].clear();
}
for(int i=1,x,y,z;i<n;i++){
scanf("%d %d %d",&x,&y,&z);
e[x].pb(y);w[x].pb(z);
e[y].pb(x);w[y].pb(z);
}
dfs(1,0);
printf("%lld\n",max(0ll,-b[1]));
}
}
C. Groundhog ans Gmaming Time
给出 n n n 条线段,每条线段有 1 2 \frac 1 2 21 的概率被选择,问选择的线段的并的长度的平方的期望。
n n n 条线段会分出 2 n − 1 2n-1 2n−1 个小区间,考虑对每个小区间求解。
由于求的是长度平方的期望,所以一个小区间的贡献是:小区间的长度 × \times × 包含这个小区间的线段的并的长度的期望 E E E。
假设现在在求解第 i i i 个小区间,令 c j c_j cj 表示同时包含 i , j i,j i,j 区间的线段数量,那么第 j j j 个区间对 E E E 的贡献为 2 c j − 1 2 n × l j \dfrac {2^{c_j}-1} {2^n}\times l_j 2n2cj−1×lj, l j l_j lj 表示第 j j j 个小区间长度,那么第 i i i 个小区间的贡献为 l i × ∑ j = 1 2 n − 1 2 c j − 1 2 n × l j l_i\times\sum_{j=1}^{2n-1}\dfrac {2^{c_j}-1} {2^n}\times l_j li×∑j=12n−12n2cj−1×lj,用线段树维护 2 c j × l j 2^{c_j}\times l_j 2cj×lj 即可。
代码如下:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define mod 998244353
#define pb push_back
int n;
struct par{
int x,y,z;}a[maxn];
vector<int> b;
const int inv2=(mod+1)/2;
int add(int x){
return x>=mod?x-mod:x;}
struct node{
int l,r,mid,val,c;node *zuo,*you;
node(int x,int y):l(x),r(y),mid(l+r>>1),val((b[y+1]-b[x])%mod),c(1){
if(x<y){
zuo=new node(l,mid);
you=new node(mid+1,r);
}else zuo=you=NULL;
}
void update(int z){
c=1ll*c*z%mod;val=1ll*val*z%mod;}
void change(int x,int y,int z){
if(l==x&&r==y)return update(z);
if(y<=mid)zuo->change(x,y,z);
else if(x>=mid+1)you->change(x,y,z);
else zuo->change(x,mid,z),you->change(mid+1,y,z);
val=1ll*add(zuo->val+you->val)*c%mod;
}
}*root;
vector<par> d[maxn];
int main()
{
scanf("%d",&n);
b.pb(0);b.pb(2e9);
for(int i=1;i<=n;i++){
scanf("%d %d",&a[i].x,&a[i].y);
b.pb(a[i].x),b.pb(++a[i].y);
}
sort(b.begin(),b.end());b.erase(unique(b.begin(),b.end()),b.end());
#define desc(x) (lower_bound(b.begin(),b.end(),x)-b.begin())
for(int i=1;i<=n;i++){
a[i].x=desc(a[i].x);a[i].y=desc(a[i].y);
d[a[i].x].pb((par){
a[i].x,a[i].y-1,2});
d[a[i].y].pb((par){
a[i].x,a[i].y-1,inv2});
}
int ans=0;root=new node(0,b.size()-2);
for(int i=0;i<b.size()-1;i++){
for(par j:d[i])root->change(j.x,j.y,j.z);
ans=add(ans+1ll*(b[i+1]-b[i])*root->val%mod);
}
ans=(ans-((long long)4e18)%mod+mod)%mod;
for(int i=1;i<=n;i++)ans=1ll*ans*inv2%mod;
printf("%d",ans);
}
D. Groundhog and Golden Apple
有一棵树,第 i i i 个节点上有一个编号为 i i i 的人,第 i i i 个人有 K i K_i Ki 次强行突破的权利,每条边有一个允许区间,如果一个人的编号在允许区间内,那么他可以直接通过这条边,否则就要强行突破,问每个人能够到达多少个点。
转化一下,对于第 i i i 个人,假如他在一条边的允许区间内,那么这条边边权为 0 0 0,否则为 1 1 1,那么权为 0 0 0 的边组成了一些连通块,若 K i = 0 K_i=0 Ki=0,那么答案就是 i i i 所在连通块的大小,否则还要加上相邻连通块的大小,可以用可撤销按秩合并并查集加分治来维护连通块。
维护连通块的同时还需要维护答案,不妨让点 1 1 1 为根,令 s 1 [ i ] s1[i] s1[i] 表示 i i i 连通块的大小, s 2 [ i ] s2[i] s2[i] 表示 i i i 的儿子连通块大小总和,那么每次合并连通块时,只需要更新秩大的块的 s 1 , s 2 s1,s2 s1,s2 以及较浅的连通块的父亲的 s 2 s2 s2 即可。
代码如下:
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010
#define pb push_back
int n,K[maxn],ans[maxn];
vector<int> e[maxn];
struct OP{
int x,y;};
vector<OP> op[maxn<<1];
void add(int id,int l,int r,int L,int R,int x,int y){
if(l==L&&r==R)return op[id].pb((OP){
x,y});
int mid=l+r>>1;
if(R<=mid)add(id<<1,l,mid,L,R,x,y);
else if(L>=mid+1)add((id<<1)+1,mid+1,r,L,R,x,y);
else add(id<<1,l,mid,L,mid,x,y),add((id<<1)+1,mid+1,r,mid+1,R,x,y);
}
int dep[maxn],Fa[maxn],top[maxn];
int s1[maxn],s2[maxn],fa[maxn];
int findfa(int x){
return x==fa[x]?x:findfa(fa[x]);}
void dfs(int x){
s1[x]=1;fa[x]=x;top[x]=x;
for(int y:e[x])if(y!=Fa[x]){
Fa[y]=x;s2[x]++;
dep[y]=dep[x]+1;
dfs(y);
}
}
struct par{
int x,y,s1_x,s2_x,top_x,fa_x,s2_fa;}zhan[maxn<<3];int t=0;
void solve(int id,int l,int r){
int last=t;
for(OP i:op[id]){
int x=i.x,y=i.y;if(dep[y]<dep[x])swap(x,y);
x=findfa(x),y=findfa(y);int father=findfa(Fa[top[x]]);
if(s1[x]>=s1[y]){
zhan[++t]=(par){
x,y,s1[x],s2[x],top[x],father,s2[father]};
s2[father]+=s1[y];
s2[x]+=s2[y]-s1[y];
s1[x]+=s1[y];
fa[y]=x;
}else{
zhan[++t]=(par){
y,x,s1[y],s2[y],top[y],father,s2[father]};
s2[father]+=s1[y];
s2[y]+=s2[x]-s1[y];
s1[y]+=s1[x];
fa[x]=y;
top[y]=top[x];
}
}
if(l==r){
int x=findfa(l);
if(!K[l])ans[l]=s1[x];
else ans[l]=s1[x]+s2[x]+s1[findfa(Fa[top[x]])];
}else{
int mid=l+r>>1;
solve(id<<1,l,mid);
solve((id<<1)+1,mid+1,r);
}
while(t>last){
int x=zhan[t].x,y=zhan[t].y;
s1[x]=zhan[t].s1_x;
s2[x]=zhan[t].s2_x;
top[x]=zhan[t].top_x;
fa[y]=y;
s2[zhan[t].fa_x]=zhan[t].s2_fa;
t--;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&K[i]);
for(int i=1,x,y,l,r;i<n;i++){
scanf("%d %d %d %d",&x,&y,&l,&r);
e[x].pb(y);e[y].pb(x);
add(1,1,n,l,r,x,y);
}
dfs(1);solve(1,1,n);
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
}
E. Groundhog Chasing Death
给出 l 1 , r 1 , l 2 , r 2 , a , b l_1,r_1,l_2,r_2,a,b l1,r1,l2,r2,a,b,计算 ∏ i = l 1 r 1 ∏ j = l 2 r 2 gcd ( a i , b j ) \prod_{i=l_1}^{r_1}\prod_{j=l_2}^{r_2} \gcd(a^i,b^j) ∏i=l1r1∏j=l2r2gcd(ai,bj)。
因为 a a a 最大 1 e 9 1e9 1e9,拥有的不同质因子个数不超过 9 9 9 个,即 a , b a,b a,b 共同拥有的质因子个数不超过 9 9 9 个,考虑对每个质因子单独求解。
对于质因子 p p p,设 a a a 包含 x x x 个 p p p, b b b 包含 y y y 个 p p p,那么就是要计算:
p ∑ i ∑ j min ( i x , j y ) p^{\sum_i\sum_j\min(ix,jy)} p∑i∑jmin(ix,jy)
设 i x ix ix 为较小值,枚举 i i i,容易算出 j j j 的范围,然后就可以统计出来。 j y jy jy 为最小值的情况类似。
代码如下:
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define mod 998244353
int l1,r1,l2,r2,a,b;
struct par{
int p,x,y;};
vector<par> A,B,C;
void solve(int x,vector<par> &vec){
for(int i=2;i*i<=x;i++){
if(x%i==0){
int tot=0;
while(x%i==0)x/=i,tot++;
vec.push_back((par){
i,tot,0});
}
}
if(x>1)vec.push_back((par){
x,1,0});
}
int ksm(int x,int y){
int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int run(int p,int x,int y){
int re=0;
for(int i=l1;i<=r1;i++){
int lim=max(l2,(int)ceil(1.0*i*x/y));//注意,这里求的是指数之和,所以模数为mod-1
if(lim<=r2)re=(re+1ll*(r2-lim+1)*i%(mod-1)*x%(mod-1))%(mod-1);
}
for(int j=l2;j<=r2;j++){
int lim=max(l1,(int)ceil(1.0*j*y/x+1e-5));//+1e-5是去掉ix=jy的情况,这种情况上面计算过了
if(lim<=r1)re=(re+1ll*(r1-lim+1)*j%(mod-1)*y%(mod-1))%(mod-1);
}
return ksm(p,re);
}
int main()
{
scanf("%d %d %d %d %d %d",&l1,&r1,&l2,&r2,&a,&b);
solve(a,A);solve(b,B);
for(par i:A)for(par j:B)if(i.p==j.p){
C.push_back((par){
i.p,i.x,j.x});break;}
int ans=1;for(par i:C)ans=1ll*ans*run(i.p,i.x,i.y)%mod;
printf("%d",ans);
}
F. Groundhog Looking Dowdy
有 n n n 天,每天给 k i k_i ki 件衣服,每件衣服有一个邋遢值,你要选 m m m 天,每天选一件衣服,使选出来的衣服的邋遢值的最大值与最小值之差最小。
一开始以为他不会卡堆,就直接用set维护一波信息,然后就T飞了……
后来发现,将所有衣服按邋遢值从小到大排序之后,就变成了:选一个区间,区间内邋遢值最大最小值差值最小,并且出现过 m m m 天的衣服。这就是个简单的尺取法了。
代码如下:
#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
#define maxn 1000010
int n,m,ans=1e9;
struct par{
int x,be;};
vector<par> a;
int occ[maxn],tot=0;
bool cmp(par x,par y){
return x.x<y.x;}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1,k,x;i<=n;i++){
scanf("%d",&k);
while(k--)scanf("%d",&x),a.push_back((par){
x,i});
}
sort(a.begin(),a.end(),cmp);
for(int l=0,r=0;l<=n;l++){
while(r<a.size()&&tot<m)if(!occ[a[r++].be]++)tot++;
if(tot<m)break;
ans=min(ans,a[r-1].x-a[l].x);
if(!--occ[a[l].be])tot--;
}
printf("%d",ans);
}
G. Groundhog Playing Scissors
给出一个凸多边形,可以绕 ( 0 , 0 ) (0,0) (0,0) 旋转,给一条直线,问将多边形随机旋转后,直线被凸多边形包含的部分长度超过 L L L 的概率。
旋转凸多边形等价于旋转直线,将角度分成若干小份,将直线依次旋转然后求与凸多边形两个交点的距离即可。
同校大佬分了 3 × 1 0 5 3\times 10^5 3×105 块就过了,但是蒟蒻要分大约 6 × 1 0 6 6\times 10^6 6×106 块并开一手long double才能过,很奇怪……(值得高兴的是,我虽然理论复杂度比他高几十倍,但是实际只多了1.5倍的时间,说明蒟蒻实现的还算优秀qwq……)
代码如下:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define double long double
const double eps=1e-10;
const double Pi=acos(-1);
struct point{
double x,y; point(){
}
point(double xx,double yy):x(xx),y(yy){
}
point operator +(const point B){
return point(x+B.x,y+B.y);}
point operator -(const point B){
return point(x-B.x,y-B.y);}
point operator *(const double B){
return point(x*B,y*B);}
double operator ^(const point B){
return x*B.x+y*B.y;}//点积
double operator *(const point B){
return x*B.y-y*B.x;}//叉积
};
double dis(point a,point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}
struct Vec{
point x,y; Vec(){
}
Vec(point xx,point yy):x(xx),y(yy){
}
double len(){
return dis(x,y);}
};
//判断直线是否穿过线段
bool cross2(Vec a,Vec b){
return ((b.x-a.x)*(a.y-a.x))*((b.y-a.x)*(a.y-a.x))<=0;}
bool cross(Vec a,Vec b){
return cross2(a,b)&&cross2(b,a);}
point meet(Vec a,Vec b){
//求直线交点
if(fabs((a.y-a.x)*(b.y-b.x))<eps)return point(-1e9,-1e9);
// if(!cross(a,b))return point(-1e9,-1e9);
point A=a.y-a.x,B=b.y-b.x,C=b.x-a.x;
double k=(B*C)/(B*A);
return a.x+A*k;
}
point Turn(point a,double Angle,double S=-3,double C=-3){
//将点旋转Angle度
if(S==-3)S=sin(Angle),C=cos(Angle);
return point(a.x*C-a.y*S,a.y*C+a.x*S);
}
int n;
point sp[maxn];
Vec d[maxn];
double L;
Vec Li;
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;i++)cin>>sp[i].x>>sp[i].y;
for(int i=0;i<n;i++)d[i]=Vec(sp[i],sp[(i+1)%n]);
cin>>L>>Li.x.x>>Li.x.y>>Li.y.x>>Li.y.y;
int now1=0,now2;
while(!cross2(Li,d[now1]))now1=now1+1==n?0:now1+1;
now2=now1+1;
while(!cross2(Li,d[now2]))now2=now2+1==n?0:now2+1;
int ans=0;
const double sz=1e-6,S=sin(sz),C=cos(sz);
int Times=ceil(2*Pi/sz);
for(int i=1;i<=Times;i++){
if(!cross2(Li,d[now1]))now1=now1+1==n?0:now1+1;
if(!cross2(Li,d[now2]))now2=now2+1==n?0:now2+1;
if(dis(meet(Li,d[now1]),meet(Li,d[now2]))>L)ans++;
Li.x=Turn(Li.x,0,S,C);Li.y=Turn(Li.y,0,S,C);
}
printf("%.4lf",1.0*ans/Times);
}
H. Groundhog Speaking Groundhogish
有一个序列,每个元素都在 1 1 1 ~ m m m 范围内,这个序列可能丢失了至多 k k k 个元素,一个序列的贡献是所有元素的价值之积,求这个序列的所有情况的贡献之和。
对于序列中每两个元素之间,考虑往里面塞元素,为了避免记重,所以不能塞和下一个元素相同的元素,其他元素都可以塞,那么塞一个的所有情况的贡献和就是 v [ i ] = ∑ j ≠ s [ i + 1 ] v a l [ j ] v[i]=\sum_{j\neq s[i+1]}val[j] v[i]=∑j=s[i+1]val[j], s s s 是原序列, v a l val val 是每个元素的价值。那么塞 k k k 个就是 v [ i ] k v[i]^k v[i]k,于是就转化成了一个完全背包问题。
容易发现这种完全背包问题可以用生成函数优化到 n log n n\log n nlogn,令 f ( i ) = 1 1 − v i x f(i)=\dfrac 1 {1-v_ix} f(i)=1−vix1,那么 ∏ f ( i ) \prod f(i) ∏f(i) 的前 k + 1 k+1 k+1 项的系数就是答案( x 0 x^0 x0 ~ x k x^k xk),可以先用分治 n t t ntt ntt 求出分母的乘积,再求逆得到 ∏ f ( i ) \prod f(i) ∏f(i)。
代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 400010
#define mod 998244353
#define bin(x) (1<<(x))
int n,m,k,a[maxn],val[maxn],v[maxn];
int add(int x){
return x>=mod?x-mod:x;}
int dec(int x){
return x<0?x+mod:x;}
void add(int &x,int y){
x=(x+y>=mod?x+y-mod:x+y);}
void dec(int &x,int y){
x=(x-y<0?x-y+mod:x-y);}
int ksm(int x,int y){
int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int inv[maxn];
struct NTT{
int w[maxn];void prep(int lg){
int N=bin(lg);
inv[1]=1;for(int i=2;i<=N;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1,wn;i<N;i<<=1){
w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
}
}
int limit,r[maxn];
void work(int lg){
for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
void dft(int *f,int lg,int type=0){
limit=bin(lg);if(type)reverse(f+1,f+limit);
for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
{
t=1ll*f[j+i+mid]*w[i+mid]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
}
}ntt;
int A[maxn],B[maxn];
struct POLY{
vector<int> a;int len;void rs(int ln){
a.resize(len=ln);}POLY(){
rs(0);}
int &operator [](int x){
return a[x];}
void dft(int *A_,int lg,int ln){
for(int i=0;i<bin(lg);i++)A_[i]=i<min(len,ln)?a[i]:0;ntt.dft(A_,lg);}
void idft(int *A_,int lg,int ln){
ntt.dft(A_,lg,1);rs(ln);for(int i=0;i<ln;i++)a[i]=1ll*A_[i]*inv[bin(lg)]%mod;}
POLY Mul(POLY b,int ln){
int lg=ceil(log2(ln*2-1));ntt.work(lg);dft(A,lg,ln);b.dft(B,lg,ln);
for(int i=0;i<bin(lg);i++)B[i]=1ll*A[i]*B[i]%mod;b.idft(B,lg,ln);return b;
}
}F,G;
POLY fenzhi_fft(int l,int r){
POLY re;
if(l==r){
re.rs(2);
re[0]=1;re[1]=mod-v[l];
return re;
}
int mid=l+r>>1;
re=fenzhi_fft(l,mid);
POLY p=fenzhi_fft(mid+1,r);
re=re.Mul(p,r-l+2);return re;
}
void getinv(POLY &f,POLY &g,int ln){
if(ln==1){
g.rs(1);g[0]=ksm(f[0],mod-2);return;}getinv(f,g,(ln+1)>>1);
int lg=ceil(log2(ln*2-1));ntt.work(lg);f.dft(A,lg,ln);g.dft(B,lg,ln);
for(int i=0;i<bin(lg);i++)B[i]=1ll*B[i]*(2-1ll*A[i]*B[i]%mod+mod)%mod;g.idft(B,lg,ln);
}
int main()
{
ntt.prep(18);
ios::sync_with_stdio(false);
while(cin>>n>>m>>k,n!=0){
for(int i=1;i<=n;i++)cin>>a[i];int sum=0;
for(int i=1;i<=m;i++)cin>>val[i],add(sum,val[i]);
a[n+1]=0;for(int i=0;i<=n;i++)v[i]=dec(sum-val[a[i+1]]);
F=fenzhi_fft(0,n);getinv(F,G,k+1);
int ans=0;
for(int i=0;i<=k;i++)add(ans,G[i]);
for(int i=1;i<=n;i++)ans=1ll*ans*val[a[i]]%mod;
cout<<ans<<endl;
}
}
I. The Crime-solving Plan of Groundhog
给出 n n n 个数,在 0 0 0 ~ 9 9 9 范围内,你要将他们组成两个没有前导零的数并使它们乘积最小。
手玩一下或者直接猜,就容易发现拿出最小的一个数,剩下的组成另一个数一定最优。
证明的话考虑一个简单情况: n = 4 n=4 n=4,此时四个数 a , b , c , d a,b,c,d a,b,c,d,满足 a ≤ b ≤ c ≤ d a\leq b\leq c\leq d a≤b≤c≤d,有两种组合方法:两位数乘两位数,三位数乘一位数。
根据一个小性质:当 a ≥ b a\geq b a≥b 时, ( a + 1 ) ( b − 1 ) = a b − a + b − 1 < a b (a+1)(b-1)=ab-a+b-1<ab (a+1)(b−1)=ab−a+b−1<ab,并且容易发现 c , d c,d c,d 不可能放在十位数,就能得到最优的两位数乘两位数方式: ( 10 a + c ) ( 10 b + d ) = 100 a b + 10 b c + 10 a d + c d (10a+c)(10b+d)=100ab+10bc+10ad+cd (10a+c)(10b+d)=100ab+10bc+10ad+cd。
三位数乘一位数的就比较显然: a × ( 100 b + 10 c + d ) = 100 a b + 10 a c + a d a\times(100b+10c+d)=100ab+10ac+ad a×(100b+10c+d)=100ab+10ac+ad。
下面的减上面的得到 10 a c − 10 b c − 9 a d − c d 10ac-10bc-9ad-cd 10ac−10bc−9ad−cd,这用脚指头看都知道这个柿子小于 0 0 0,所以三位数乘一位数更优。
n > 4 n>4 n>4 的情况类似,于是就做完了。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
int T,n,a[maxn];
int ans[maxn],t;
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
int st=0;while(a[st+1]==0)st++;
t=0;memset(ans,0,(n+5)<<2);
for(int i=n;i>=st+3;i--)ans[++t]=a[i];
for(int i=1;i<=st;i++)ans[++t]=0;
ans[++t]=a[st+2];
for(int i=1;i<=t;i++)ans[i]*=a[st+1];
for(int i=1;i<=t;i++)if(ans[i]>9){
ans[i+1]+=ans[i]/10;
ans[i]%=10;
if(i==t)t++;
}
for(int i=t;i>=1;i--)printf("%d",ans[i]);printf("\n");
}
}
J. The Escape Plan of Groundhog
给出一个 01 01 01 矩阵,问有多少个子矩阵满足:边界全是 1 1 1,内部 0 , 1 0,1 0,1 数量相差不超过 1 1 1。
将 0 0 0 转成 − 1 -1 −1,枚举矩形上下边界,内部用前缀和维护一下就好了,就是看有多少对列 i , j i,j i,j 满足 ∣ s u m [ j − 1 ] − s u m [ i ] ∣ ≤ 1 |sum[j-1]-sum[i]|\leq 1 ∣sum[j−1]−sum[i]∣≤1, s u m [ i ] sum[i] sum[i] 表示上下边界内前 i i i 列所有元素之和,用一个桶维护 s u m [ i ] sum[i] sum[i] 即可。
代码如下:
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 510
#define off 250000//偏移量,处理负数
int n,m,a[maxn][maxn];
int sum[maxn][maxn];
int occ[maxn*maxn<<1];//桶
vector<int> pos;
long long ans=0;
int getsum(int x,int y,int xx,int yy){
if(x>xx||y>yy)return 0;
return sum[xx][yy]-sum[xx][y-1]-sum[x-1][yy]+sum[x-1][y-1];
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);if(!a[i][j])a[i][j]=-1;
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
}
}
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
for(int k=1;k<=m;k++){
if(a[i][k]==-1||a[j][k]==-1){
for(int p:pos)occ[getsum(i+1,1,j-1,p)+off]--;
pos.clear();
}else{
if(getsum(i,k,j,k)==j-i+1){
int p=getsum(i+1,1,j-1,k-1);
ans+=occ[p-1+off]+occ[p+off]+occ[p+1+off];
p=getsum(i+1,1,j-1,k);
occ[p+off]++;pos.push_back(k);
}
}
}
for(int p:pos)occ[getsum(i+1,1,j-1,p)+off]--;
pos.clear();
}
}
printf("%lld",ans);
}
K.The Flee of Groundhog
给出一棵树,给出 A , B A,B A,B 两人初始位置, A A A 的速度是每秒两条边, B B B 的速度是每秒一条边,现在问 A A A 最多要用多少时间才能追到 B B B。
一开始以为 B B B 在前 t t t 秒也可以乱走,写完之后发现连样例都过不了,重新读题后发现 B B B 一开始只会往 A A A 走……
以 A A A 的初始点为根,那么 B B B 肯定往它最深的那个子孙那里走,然后就是个简单的追击问题了。
代码如下:
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 100010
int n,t;
struct edge{
int y,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y){
e[++len]=(edge){
y,first[x]};first[x]=len;}
int fa[maxn],dep[maxn],max_dep[maxn];
void dfs(int x){
max_dep[x]=dep[x];
for(int i=first[x],y;i;i=e[i].next)
if((y=e[i].y)!=fa[x]){
dep[y]=dep[x]+1;fa[y]=x;dfs(y);
max_dep[x]=max(max_dep[x],max_dep[y]);
}
}
int main()
{
scanf("%d %d",&n,&t);
for(int i=1,x,y;i<n;i++){
scanf("%d %d",&x,&y);
buildroad(x,y);buildroad(y,x);
}
dep[1]=0;fa[1]=0;dfs(1);
if(dep[n]<=t)return printf("0"),0;
int pos=n;for(int i=1;i<=dep[n]-t;i++)pos=fa[pos];
dep[n]=0;fa[n]=0;dfs(n);int ans=0;
if(max_dep[pos]-dep[pos]>=dep[pos])printf("%d",dep[pos]);
else printf("%d",(int)ceil((2*dep[pos]-max_dep[pos])/2.0)+max_dep[pos]-dep[pos]);
}