2020牛客暑期多校训练营(第三场)题解
文章目录
- A. Clam ans Fish
- B. Classical String Problem
- C. Operation Love
- D. Points Construction Problem
- E. Two Matchings
- F. Fraction Construction
- G. Operating on the graph
- H. Sort the Strings Revision
- I. Sorting the Array
- J. Operating on the Tree
- L. Problem L is the Only Lovely Problem
A. Clam ans Fish
有 n n n 个鱼塘,每个鱼塘是四种鱼塘中的一种,这四种分别是啥都没、有鱼饵、有鱼、都有,每次可以选择四种操作之一:不动、拿鱼、拿鱼饵、用鱼饵钓鱼(可以在没鱼的鱼塘里钓),求最后的最大鱼数。
有饵没鱼的肯定拿饵,有鱼没饵的肯定拿鱼,啥都没有的肯定尝试钓鱼,都有的肯定拿鱼,因为不知道鱼饵后面能不能有机会钓。
然后后面还剩了 k k k 个饵,可以考虑只要前 k 2 \dfrac k 2 2k 个,在遇到后面的 k 2 \dfrac k 2 2k 个饵时,由于是多余的,就不拿了,操作换成钓鱼。
代码如下:
#include int T,n;char s[2000010];int main()
{scanf("%d",&T);while(T--){scanf("%d %s",&n,s+1);int sum1=0,sum2=0;for(int i=1;i<=n;i++)switch(s[i]){case '0':if(sum1)sum1--,sum2++;break;case '1':sum1++;break;case '2':sum2++;break;case '3':sum2++;break;}printf("%d\n",sum2+sum1/2);}
}
B. Classical String Problem
给出一个字符串,有三种操作,1、将前 k k k 个字符移到后面,2、将后 k k k 个字符移到前面,3、询问第 k k k 个字符是谁。
前两个操作其实可以看成字符串的整体左移和右移,所以记录一下字符串的开头在哪里可以搞询问了。
代码如下:
#include
#include char s[2000010];
int n,m;int main()
{scanf("%s",s+1);n=strlen(s+1);scanf("%d",&m);char id[2];int x,now=1;while(m--){scanf("%s %d",id,&x);if(id[0]=='M'){now-=x;if(now<1)now+=n;if(now>n)now-=n;}else{x=x-now+1;if(x<1)x+=n;printf("%c\n",s[x]);}}
}
C. Operation Love
给出一个手掌,问这是右手还是左手。
我的做法是先找到手掌底部的两个点,只有他们的距离是 9 9 9,很好找,分别记为第 i i i 和 i + 1 i+1 i+1 个点。
然后再判断一下第 i − 1 i-1 i−1 个点与 i i i 的距离,看看是 6 6 6 还是 8 8 8,这样能判断大拇指位置。
然后再用叉积判一下点是顺时针给的还是逆时针给的即可。
代码如下:
#include
#include
#define eps 0.1int T;
struct point{double x,y;double det(point B){return x*B.y-y*B.x;}point operator -(const point B){return (point){x-B.x,y-B.y};}
}a[21];
int pre(int x){return x==1?20:x-1;}
int next(int x){return x==20?1:x+1;}
double dis(point x,point y){return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));}int main()
{scanf("%d",&T);while(T--){for(int i=1;i<=20;i++)scanf("%lf %lf",&a[i].x,&a[i].y);for(int i=1;i<=20;i++)if(fabs(dis(a[i],a[next(i)])-9.0)<eps){double x=dis(a[i],a[pre(i)]);int ans;if(fabs(x-6.0)<eps)ans=1;else ans=0;if((a[next(i)]-a[i]).det(a[next(next(i))]-a[i])<0)ans^=1;if(ans==1)printf("right\n");else printf("left\n");break;}}
}
D. Points Construction Problem
要求将平面上 n n n 个点染成黑色,其他都是白色,存在恰好有 m m m 对点 (X,Y),满足点 X , Y X,Y X,Y 相邻,且一个为黑一个为白。
考虑将这 n n n 个点一行放 n \sqrt n n 个堆积起来,此时的答案是最小的。
然后逐渐将最上面那行的最右边那个放到最下面那行的最右边,每次移动一个可以使答案 + 2 +2 +2,直到所有点都在一条直线上并且都相邻。
然后再从左到右考虑第 i i i 个点,将第 i i i ~ n n n 个点整体右移一位,即让 i i i 和 i − 1 i-1 i−1 中间空出一位,这样又可以使答案 + 2 +2 +2。
在做上面这个过程时,当答案等于 m m m 时,输出即可。
代码如下:
#include
#include
#include
#include
using namespace std;
#define maxn 60int T,n,m,ans;
struct point{int x,y;}s[maxn];
bool v[maxn][maxn];
void add(int x,int y)
{if(v[x-1][y])ans--;else ans++;if(v[x+1][y])ans--;else ans++;if(v[x][y-1])ans--;else ans++;if(v[x][y+1])ans--;else ans++;v[x][y]=true;
}
void print()
{printf("Yes\n");for(int i=1;i<=n;i++)printf("%d %d\n",s[i].x,s[i].y);
}int main()
{scanf("%d",&T);while(T--){scanf("%d %d",&n,&m);ans=0;if(m%2){printf("No\n");continue;}memset(v,false,sizeof(v));int len=sqrt(n),x=1,y=1;for(int i=1;i<=n;i++){s[i]=(point){x,y};add(x,y);y++;if(y>len)x++,y=1;}if(m<ans||m>4*n){printf("No\n");continue;}if(m==ans){print();continue;}x=1,y=len+1;for(int i=n;i>len;i--){if(s[i].y!=1)ans+=2;s[i]=(point){x,y};y++;if(ans==m)break;}if(ans==m){print();continue;}x=1,y=1;for(int i=1;i<=n;i++){s[i]=(point){x,y};y++;if(ans<m)y++,ans+=2;}print();}
}
E. Two Matchings
称 1 1 1 ~ n n n 的一个排列 p p p 是“配对的”当且仅当 ∀ i , p i ≠ i , p p i = i \forall i,p_i\neq i,p_{p_i}=i ∀i,pi=i,ppi=i,现在有一个长度为 n n n 的序列 A A A,定义一个排列的代价为 ∑ i = 1 n ∣ a i − a p i ∣ / 2 \sum_{i=1}^n |a_i-a_{p_i}|/2 ∑i=1n∣ai−api∣/2,现在要找两个完全不同的“配对的”排列使得总代价最小。
“配对的”排列其实就是将 1 1 1 ~ n n n 这个排列每一位都与其他位进行交换,且每一位仅交换一次,如 3 , 4 , 1 , 2 3,4,1,2 3,4,1,2 就是一个“配对的”排列, 1 1 1 与 3 3 3 交换了, 2 2 2 与 4 4 4 交换了,下面称交换的两位是相互配对的。
那么一个排列的代价其实就是将每个配对的 A i A_i Ai 之差加起来,那么显然最小的一个方案就是将 A A A 进行排序,然后相邻的两个进行配对。
考虑第二小的方案,由于两个方案不能有任何一个配对相同,所以此时只能考虑相邻的 4 4 4 个进行相互配对和相邻 6 6 6 个进行相互配对。相邻 8 8 8 个以上的配对就不需要考虑了,因为他们拆成 4 4 4 个和 6 6 6 个的一定不会更劣,然后做一次dp即可。
相邻 4 4 4 个的最优配对方案为 ( 1 , 3 ) , ( 2 , 4 ) (1,3),(2,4) (1,3),(2,4),相邻 6 6 6 个的最优配对方案为 ( 1 , 3 ) , ( 2 , 5 ) , ( 4 , 6 ) (1,3),(2,5),(4,6) (1,3),(2,5),(4,6)。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 200010
#define ll long longint T,n,a[maxn];
ll f[maxn],ans;
ll calc1(int x){return a[x+2]-a[x]+a[x+3]-a[x+1];}
ll calc2(int x){return a[x+2]-a[x]+a[x+5]-a[x+3]+a[x+4]-a[x+1];}int main()
{scanf("%d",&T);while(T--){scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]),f[i]=2147483647;sort(a+1,a+n+1);ans=0;for(int i=1;i<=n;i+=2)ans+=a[i+1]-a[i];for(int i=0;i<=n;i++){if(i+4<=n)f[i+4]=min(f[i+4],f[i]+calc1(i+1));if(i+6<=n)f[i+6]=min(f[i+6],f[i]+calc2(i+1));}printf("%lld\n",ans+f[n]);}
}
F. Fraction Construction
找两个分数 c d , e f \dfrac c d,\dfrac e f dc,fe,满足 c d − e f = a b \dfrac c d-\dfrac e f=\dfrac a b dc−fe=ba,且 d , f < b d,fd,f<b。
先将 a , b a,b a,b 化简为 a ′ , b ′ a',b' a′,b′,然后转化一下上面的柿子:
c d − e f = a ′ b ′ c f − e d d f = a ′ b ′ \frac c d-\frac e f=\frac {a'} {b'}\\ \dfrac {cf-ed} {df}=\frac {a'} {b'} dc−fe=b′a′dfcf−ed=b′a′
下面不妨令 a ′ = c f − e d , b ′ = d f a'=cf-ed,b'=df a′=cf−ed,b′=df。
假如 b ′ < b b'b′<b,那么存在一组解 c = a ′ + b ′ , d = b ′ , f = 1 , e = 1 c=a'+b',d=b',f=1,e=1 c=a′+b′,d=b′,f=1,e=1。
如果 b ′ = b b'=b b′=b,那么 d d d 就不能等于 b ′ b' b′ 了,因为 d d d 要小于 b b b。
考虑枚举 d , f d,f d,f,因为要让 c f − e d = a ′ cf-ed=a' cf−ed=a′ 有解,所以 gcd ( d , f ) ∣ a ′ \gcd(d,f)|a' gcd(d,f)∣a′,而 gcd ( a ′ , b ′ ) = 1 \gcd(a',b')=1 gcd(a′,b′)=1,所以 gcd ( d , f ) = 1 \gcd(d,f)=1 gcd(d,f)=1。
枚举出 d , f d,f d,f 后,直接扩欧求 c , e c,e c,e 即可。
代码如下:
#include
#include
#include
using namespace std;
#define ll long longll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
void exgcd(ll a,ll b,ll &x,ll &y){if(!b){x=1;y=0;return;}exgcd(b,a%b,y,x);y-=a/b*x;
}
ll T,a,b,c,d,e,f;int main()
{scanf("%lld",&T);while(T--){scanf("%lld %lld",&a,&b);d=-1;ll p=gcd(a,b);if(p>1){printf("%lld %lld 1 1\n",a/p+b/p,b/p);continue;}for(ll i=2;i*i<=b;i++)if(b%i==0&&gcd(i,b/i)==1){d=i,f=b/i;break;}if(d==-1){printf("-1 -1 -1 -1\n");continue;}exgcd(f,d,c,e);e=-e;while(c<=0||e<=0)c+=d,e+=f;c*=a;e*=a;printf("%lld %lld %lld %lld\n",c,d,e,f);}
}
G. Operating on the graph
给出一张图,一开始第 i i i 个点属于第 i i i 个团队,有 q q q 次操作,每次选择一个团队 i i i,将与其相邻的团队都合并到 i i i 团队来,问最后每个人属于哪个团队。
显然每条边只能被扩展一次,用 set \text{set} set 啥的维护一下每次暴力拓展就好了。
需要注意一下空间,代码如下:
#include
#include
#include
#include
#include
using namespace std;
#define maxn 800010int T,n,m,q;
int fa[maxn];
int findfa(int x){return x==fa[x]?x:fa[x]=findfa(fa[x]);}
vector<int>s[maxn];
void merge(vector<int>&x,vector<int>&y)
{if(x.size()<y.size())x.swap(y);for(int i=0;i<y.size();i++)x.push_back(y[i]);vector<int>K;y.swap(K);//释放y内的空间
}int main()
{scanf("%d",&T);while(T--){scanf("%d %d",&n,&m);for(int i=1;i<=n;i++)s[i].clear(),fa[i]=i;for(int i=1,x,y;i<=m;i++)scanf("%d %d",&x,&y),x++,y++,s[x].push_back(y),s[y].push_back(x);scanf("%d",&q);while(q--){int x;scanf("%d",&x);x++;if(x!=fa[x])continue;vector<int>vec;vec.swap(s[x]);for(int i=0,y;i<vec.size();i++)if(x!=(y=findfa(vec[i])))merge(s[x],s[y]),fa[y]=x;}for(int i=1;i<=n;i++)printf("%d ",findfa(i)-1);printf("\n");}
}
H. Sort the Strings Revision
你有 n + 1 n+1 n+1 个长度为 n n n 的字符串, s 0 s_0 s0 的第 i i i 位是数字 i m o d 10 i\bmod 10 imod10, s i ( i > 0 ) s_i(i>0) si(i>0) 由 s i − 1 s_{i-1} si−1 将第 p i − 1 p_{i-1} pi−1 位的数字换成 d i − 1 d_{i-1} di−1 得到,保证 p p p 是 0 0 0 ~ p − 1 p-1 p−1 的一个排列,现在要你将 s 0 s_0 s0 ~ s n s_n sn 进行排序,输出它们的排名。
由于 p p p 是 0 0 0 ~ p − 1 p-1 p−1 的一个排列,说明每个位置只会被替换一次,而由于是按字典序排序,所以在前面的位会比后面的位影响要大,当更改了 0 0 0 位置后,就可以确定更改前的串与更改后的串的大小关系,所以可以考虑分治,每次处理区间内最前面的位置。
求区间最值可以用笛卡尔树,代码如下:
#include
#include
#include
using namespace std;
#define maxn 2000010
#define inf 999999999int T,n,seed,A,B,MOD;
int p[maxn],d[maxn],rk[maxn],id=0;
int zhan[maxn],t,ls[maxn],rs[maxn];
void build(){zhan[t=1]=0;for(int i=1;i<n;i++){while(t&&p[zhan[t]]>p[i])t--;if(t)ls[i]=rs[zhan[t]],rs[zhan[t]]=i;else ls[i]=zhan[1];zhan[++t]=i;}
}
void solve(int l,int r,int pos){if(l>=r||p[pos]==inf){for(int i=l;i<=r;i++)rk[i]=id++;return;}if(p[pos]%10>d[pos])solve(pos+1,r,rs[pos]),solve(l,pos,ls[pos]);else solve(l,pos,ls[pos]),solve(pos+1,r,rs[pos]);
}
#define mod 1000000007
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 main()
{scanf("%d",&T);while(T--){scanf("%d",&n);scanf("%d %d %d %d",&seed,&A,&B,&MOD);for(int i=0;i<n;i++)p[i]=i;for(int i=1;i<n;i++){swap(p[seed%(i+1)],p[i]);seed=(1ll*seed*A+B)%MOD;}scanf("%d %d %d %d",&seed,&A,&B,&MOD);for(int i=0;i<n;i++){d[i]=seed%10;seed=(1ll*seed*A+B)%MOD;}for(int i=0;i<n;i++)if(p[i]%10==d[i])p[i]=inf;build();id=0;solve(0,n,zhan[1]);int ans=0;for(int i=0;i<=n;i++)ans=(ans+1ll*rk[i]*ksm(10000019,i)%mod)%mod;printf("%d\n",ans);}
}
I. Sorting the Array
有一个长度为 n n n 的序列 b b b,定义 f ( n , b , m ) f(n,b,m) f(n,b,m) 表示进行
for i=1 to n-m+1 do sort(b[i]~b[i+m-1])后的序列,现在给出 r e t ret ret 序列以及 k k k,要你求出满足 f ( n , b , m ) = r e t f(n,b,m)=ret f(n,b,m)=ret 的 b b b 中字典序第 k k k 小的 b b b 是什么。
有一个较容易发现的性质,对于一个 i i i,假如存在 j ∈ [ 1 , i ) j\in[1,i) j∈[1,i) 满足 r e t [ j ] > r e t [ i ] ret[j]>ret[i] ret[j]>ret[i],那么 b [ i + m − 1 ] b[i+m-1] b[i+m−1] 一定等于 r e t [ i ] ret[i] ret[i]。
证明的话,考虑换个角度来看上面那个每m位sort一次操作——这相当于在维护一个大小为 m − 1 m-1 m−1 的集合,然后从左边第 m m m 位开始,每次加入一个数进集合,然后将最小的数丢掉放到最左边,那么 r e t [ i ] ret[i] ret[i] 就是加入了 b [ i + m − 1 ] b[i+m-1] b[i+m−1] 之后丢掉的数。
对于一个 i i i,假如存在 j ∈ [ 1 , i ) j\in[1,i) j∈[1,i) 满足 r e t [ j ] > r e t [ i ] ret[j]>ret[i] ret[j]>ret[i],那么说明集合在经过第 j j j 位后,里面所有的数都大于 r e t [ j ] ret[j] ret[j] 了(否则 r e t [ j ] ret[j] ret[j] 不可能被踢出来),而 r e t [ i ] < r e t [ j ] ret[i]
我们发现,这些固定下来的位,是不会对集合产生任何贡献的,所以把他们删掉再求解也是可以的,假设删掉了 n − c n-c n−c 个数,那么现在的问题变成了求第 k k k 小的满足 f ( c , b , m ) = r e t f(c,b,m)=ret f(c,b,m)=ret 的 b b b。
可以发现,删掉固定的位后,剩下的 r e t ret ret 一定是一个严格上升序列,由于我们只关心其相对大小,所以下面认为 r e t [ i ] = i ret[i]=i ret[i]=i。
先考虑怎么求 b b b 的总方案数,由于 r e t [ i ] = i ret[i]=i ret[i]=i,所以sort完 1 1 1 ~ i + m − 1 i+m-1 i+m−1 后一定考虑过 i i i,即 i i i 只能放在 1 1 1 ~ i + m − 1 i+m-1 i+m−1 之间,前面已经放过 i − 1 i-1 i−1 个数了,剩下就只剩 m i n ( m , c − i ) min(m,c-i) min(m,c−i) 个位置可以考虑,总方案数为 ∏ i = 1 c min ( m , c − i ) \prod_{i=1}^c \min(m,c-i) ∏i=1cmin(m,c−i)。
于是可以考虑求出一个最大的 v v v 满足 ∏ i = v c min ( m , c − i ) ≥ k \prod_{i=v}^c \min(m,c-i)\geq k ∏i=vcmin(m,c−i)≥k,那么对于 i < v i
容易发现剩下的 c − v + 1 c-v+1 c−v+1 位不会超过 log k \log k logk 位,于是可以暴力考虑每一位填什么,然后统计一下后面的方案数与 k k k 的大小关系即可,时间复杂度大概是 O ( n log 3 k ) O(n\log^3k) O(nlog3k),有一个地方可以优化,即枚举每一位填什么时,从一个数换成另一个数,可以 O ( 1 ) O(1) O(1) 计算出方案数,那么时间复杂度就优化到了 O ( n log 2 k ) O(n\log^2k) O(nlog2k)(大概)。
具体看代码吧:
#include
#include
#include
using namespace std;
#define maxn 800010
#define ll long longint T,n,m,ret[maxn];ll k;
int pos[maxn],pn,val[maxn],vn;
int d[maxn],ans[maxn];int main()
{scanf("%d",&T);while(T--){scanf("%d %d %lld",&n,&m,&k);for(int i=1;i<=n;i++)scanf("%d",&ret[i]);int last=0;pn=vn=0;for(int i=1;i<m;i++)pos[++pn]=i;for(int i=1;i<=n-m+1;i++){if(ret[i]>last){last=val[++vn]=ret[i];pos[++pn]=i+m-1;}else ans[i+m-1]=ret[i];//找到固定的位}for(int i=n-m+2;i<=n;i++)val[++vn]=i;//pos记录ans中没固定的位,val记录没固定的数k--;ll prod=1;int num=pn;//找到最大的vfor(int i=1;i<=pn;i++){prod*=min(i,m);if(prod>k){num=i;break;}}for(int i=1;i<=pn-num;i++)ans[pos[i]]=val[i];for(int i=1;i<=num;i++)d[pn-i+1]=min(i,m);//d[i]表示i有多少个位置能放for(int i=pn-num;i<=pn;i++){if(!k){for(int j=i;j<=pn;j++)ans[pos[j]]=val[j];break;}ll tmp=1;for(int j=i+1;j<=pn;j++)tmp*=d[j];for(int j=i;j<=pn;j++){if(tmp>k){ans[pos[i]]=val[j];if(j!=i){rotate(val+i,val+j,val+j+1);rotate(d+i,d+j,d+j+1);}break;}k-=tmp;d[j]--;//如果第i位不放j,那么一定会放一个比j大的数,那么j能放的位置就减少了1个tmp=tmp/d[j+1]*d[j];//上面所说的O(1)求方案数}}for(int i=1;i<=n;i++)printf("%d ",ans[i]);printf("\n");}
}
J. Operating on the Tree
给出一棵树,以它为G题中的图,对于一个 1 1 1 ~ n n n 的排列,定义 f ( p ) f(p) f(p) 为将这个排列作为G题的操作序列,有多少个数 i i i 满足操作到 p i p_i pi 时,团队 p i p_i pi 不为空,求 ∑ p ∈ S f ( p ) \sum_{p\in S} f(p) ∑p∈Sf(p), S S S 是 1 1 1 ~ n n n 的全排列。
做法膜的这位大佬,由于自己和大佬码风类似所以膜起来很舒适qwq。
注意到每个数只会被操作一次,是个重要性质。
假如一个点在操作时不为空,那么称其为好点,否则为坏点,那么有这样的限制:
- 每个坏点肯定和一个比他大的好点相邻
- 任意两个好点不相邻
这里的A比B大是指,在排列 p p p 中 A A A 在 B B B 的前面。
这样我们就可以 dp \text{dp} dp 了,令 f [ i ] [ 0 / 1 / 2 ] [ j ] f[i][0/1/2][j] f[i][0/1/2][j] 表示,第 i i i 个点是 好点/已有相邻的比 i i i 大的好点的坏点/未有相邻的比 i i i 大的好点的坏点,并且子树内有 j j j 个点比 i i i 大的方案数, g [ i ] [ 0 / 1 / 2 ] [ j ] g[i][0/1/2][j] g[i][0/1/2][j] 则表示这些方案的总贡献。
转移的时候讨论一下 自己是 0 / 1 / 2 × 0/1/2~\times 0/1/2 × 儿子是 0 / 1 / 2 0/1/2 0/1/2,一共 9 9 9 种情况即可,这个不难想。
转移时还要注意的就是求方案数上的细节,具体看代码:
#include
#include
#include
#include
using namespace std;
#define maxn 2010
#define mod 998244353int T,n;
vector<int>e[maxn];
int C[maxn][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);}
void work()
{for(int i=0;i<=maxn-10;i++){C[i][0]=1;for(int j=1;j<=i;j++){C[i][j]=add(C[i-1][j-1]+C[i-1][j]);}}
}
int f[maxn][3][maxn],g[maxn][3][maxn];
int size[maxn],tmpf[3][maxn],tmpg[3][maxn];
void dfs(int x){size[x]=1;f[x][0][0]=f[x][2][0]=1;//为了方便,下面称第2类点为半坏点(坏点但未有比他大的好点相邻)for(int y:e[x]){dfs(y);for(int i=0;i<3;i++)for(int j=1;j<size[y];j++){//为了方便转移,这里给f/g[y]求个前缀和,此时f[y]的意义稍有变化,下面别搞混了//要注意的是f[x]没有求前缀和,他的意义不变add(f[y][i][j],f[y][i][j-1]);add(g[y][i][j],g[y][i][j-1]);}for(int i=0;i<size[x];i++)for(int j=0;j<=size[y];j++){//枚举x的子树内有i+j个点比x大,其中j个在y的子树内,另外i个在x前面的子树内int sit=1ll* C[i+j][j] * C[size[x]+size[y]-1-i-j][size[y]-j] %mod;//在当前长度为size[x]+size[y]的排列中给y的子树内的点找个位置//比x大的j个只能放在x前面的i+j个位置里,于是从前面i+j个位置里选j个,比x小的类似for(int t1=0;t1<3;t1++)for(int t2=0;t2<3;t2++){//先讨论y在x前面的情况,此时y子树内有j个比x大,那么比y大的就至多有j-1个(因为有一些比x大的可能在排列在(y,x)之间)int fy=j?f[y][t2][j-1]:0;int gy=j?g[y][t2][j-1]:0;int fn=1ll*sit*fy%mod*f[x][t1][i]%mod;//方案数和总贡献int gn=1ll*sit*( 1ll*fy*g[x][t1][i]%mod + 1ll*f[x][t1][i]*gy%mod )%mod;if(t1==0){//x是好点if(t2==1){//y在x前面,所以只能是坏点add(tmpf[0][i+j],fn);add(tmpg[0][i+j],gn);}}else if(t1==1){//x是坏点if(t2<2){//y可以是好点或坏点,但不可能是半坏点add(tmpf[1][i+j],fn);add(tmpg[1][i+j],gn);}}else{//x是半坏点if(t2==0){//假如y是好点,那么x就从半坏点变成了坏点add(tmpf[1][i+j],fn);add(tmpg[1][i+j],gn);}if(t2==1){//否则不变add(tmpf[2][i+j],fn);add(tmpg[2][i+j],gn);}}//然后讨论x在y前面的情况,由于y子树内比x大的有j个,这j个也必定比y大,再考虑上(x,y)内的,则y子树内至少有j个比y大fy=dec(f[y][t2][size[y]-1]-fy);gy=dec(g[y][t2][size[y]-1]-gy);fn=1ll*sit*fy%mod*f[x][t1][i]%mod;gn=1ll*sit*( 1ll*fy*g[x][t1][i]%mod + 1ll*f[x][t1][i]*gy%mod )%mod;if(t1==0){//此时由于y在x的后面,所以y影响不到x,只需要讨论情况是否合法即可if(t2>0){add(tmpf[0][i+j],fn);add(tmpg[0][i+j],gn);}}else if(t1==1){if(t2<2){add(tmpf[1][i+j],fn);add(tmpg[1][i+j],gn);}}else{if(t2<2){add(tmpf[2][i+j],fn);add(tmpg[2][i+j],gn);}}}}size[x]+=size[y];for(int i=0;i<3;i++)for(int j=0;j<size[x];j++){f[x][i][j]=tmpf[i][j];g[x][i][j]=tmpg[i][j];tmpf[i][j]=tmpg[i][j]=0;}}for(int i=0;i<size[x];i++)add(g[x][0][i],f[x][0][i]);//算上x的贡献
}int main()
{work();scanf("%d",&T);while(T--){scanf("%d",&n);for(int i=1;i<=n;i++){e[i].clear();for(int j=0;j<3;j++)for(int k=0;k<=n;k++)f[i][j][k]=0,g[i][j][k]=0;}for(int i=2,fa;i<=n;i++){scanf("%d",&fa);fa++;e[fa].push_back(i);}dfs(1);int ans=0;for(int i=0;i<=n;i++){add(ans,g[1][0][i]);add(ans,g[1][1][i]);}printf("%d\n",ans);}
}
L. Problem L is the Only Lovely Problem
判断一个字符串开头是否为 lovely,不在乎大小写。
签到题,代码如下:
#include char s[1000010];int main()
{scanf("%s",s+1);for(int i=1;i<=6;i++)if(s[i]>='A'&&s[i]<='Z')s[i]=s[i]-'A'+'a';if(s[1]=='l'&&s[2]=='o'&&s[3]=='v'&&s[4]=='e'&&s[5]=='l'&&s[6]=='y')printf("lovely");else printf("ugly");
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
