好久没有做Codeforces了,今天心血来潮做了一发。我觉得我缺少解题时灵活变通的能力,明明好多题目我是会的,但是有的时候想不到那个梗(不会变通,转化等价题意),错失了机会。我觉得这个能力需要提升的话,一个必须多刷题目,多多见识各种各样的思路,还有就是应该多参加一些(大的小的)比赛。所以,在退役之前(或者一直到自己找到实习单位)多多参加各种各样的比赛来锻炼自己。
A. Memory and Crow
分析:签到题嘛,比较简单。根据给出的公式,可以推导出 bi=ai+ai+1,1≤i<n 且 bn=an
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define maxn 100000+10
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define pii pair
#define mp make_pair
#define FI first
#define SE second
#define IT iterator
#define PB push_back
#define Times 10
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const double eps = 1e-10;
const double pi = acos(-1.0);
const ll mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const ll INF = (ll)1e18+300;
const double delta = 0.98;
int dat[maxn];
int main(){
int n;
scanf("%d",&n);
for(int i=0;iscanf("%d",&dat[i]);
for(int i=0;i1;i++)
dat[i]+=dat[i+1];
int cnt=0;
for(int i=0;iif(cnt)putchar(' ');
cnt=1;
printf("%d",dat[i]);
}
puts("");
return 0;
}
B. Memory and Trident
分析:同签到题。U和D是一对,不管他们出现在字符串的那里,都要相互抵消,L和R一样。那么,分别求出L与R的差值,U和D的差值,相加除以2,结果就是需要变化的最小步数。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define maxn 100000+10
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define pii pair
#define mp make_pair
#define FI first
#define SE second
#define IT iterator
#define PB push_back
#define Times 10
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const double eps = 1e-10;
const double pi = acos(-1.0);
const ll mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const ll INF = (ll)1e18+300;
const double delta = 0.98;
char dat[maxn];
int main(){
scanf("%s",dat);
int len=strlen(dat);
if(len&1){
puts("-1");
return 0;
}
int a=0,b=0;
for(int i=0;iif(dat[i]=='U')a++;
else if(dat[i]=='D')a--;
else if(dat[i]=='L')b++;
else b--;
a=abs(a),b=abs(b);
printf("%d\n",(a+b)/2);
return 0;
}
C. Memory and De-Evolution
分析:这应该不算签到了,应该算一道简单模拟。将(x,x,x)转换到(y,y,y),那么类似的可以反过来考虑将(y,y,y)转化到(x,x,x)。我们尽量要快速变化,那么变化最小边就是合理的选择(如果变化其他的边,变化的跨度就小,如果变化本来就小的边,那么变化的跨度就会很大)。每次将最小的边尽可能的变大,就是变成次大边和最大边的和减一,但是又不能比目标大,如果大于目标只能改成等于目标。我们发现这样是以斐波那契的速度向大数推进,基本只需要30+步就可以从3推到100000,复杂度没有问题。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define maxn 100000+10
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define pii pair
#define mp make_pair
#define FI first
#define SE second
#define IT iterator
#define PB push_back
#define Times 10
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const double eps = 1e-10;
const double pi = acos(-1.0);
const ll mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const ll INF = (ll)1e18+300;
const double delta = 0.98;
int main(){
int a,b,c,x,y;
scanf("%d %d",&y,&x);
a=b=c=x;
int num=0;
do{
num++;
a=(b+c)-1;
if(a>y)a=y;
swap(a,c);
swap(a,b);
}while(!(a==b&&b==c));
printf("%d\n",num);
return 0;
}
D. Memory and Scores
分析:这是一道比较难的题目,需要重点说下。
题意:Memory和Lexa玩拼点游戏,已知Memory开始有a点,Lexa开始有b点。游戏一共有t轮,每轮每个人在[-k,k]之间选择一个整数加到自己的点数上。问最后Memory的点数大于Lexa的点数有多少种可能?
做法:一开始确实想到了DP,而且还尝试着写了一下。但是因为写法太烂了,100左右的数据就TLE了。看了一下别人的代码用到了前缀和,看了好几遍看不懂,才知道这种写法不是我这种DP小白能看懂的。然后我找到了另一种做法——母函数做法,一看就看懂了,简单说说我的理解。
首先,从题意入手,两人之间的最后总分分别是多少没有意义,有意义的两人总分差值是多少,从差值的正负可以判断最后的胜利。那么我们需要观察的是a-b(或者b-a),而不是a和b。然后考虑之后的t轮操作,我们发现两个人对差值进行t轮操作,就相当于一个人对差值进行2t轮操作。那么题目就转换成a-b加上2t个[-k,k]之间的整数,最后结果大于0的可能方案数?
我们用母函数构造一轮操作,就是 (x−k+x−k+1+...+x−1+1+x1+x2+...+xk) 负数的话,很难分辨,那么我们每一轮乘以 xk ,得到 1+x+x2+...+x2k−1+x2k ,那么2t轮操作就是 (1+x+x2+...+x2k−1+x2k)2t ,那么最后 xj 的系数就表示加了 2t 轮之后,a-b加上了 j−2kt 的可能方案数。那么显然我们要求的是 0<a−b+j−2kt 的所有情况的系数和(当然还要满足 j≤4kt ,即要满足 b−a+2kt<j≤4kt )。
考虑怎么求解系数
(1+x+x2+...+x2k−1+x2k)2t=(1−x2k+11−x)2t=(1−x2k+1)2t(11−x)2t=(1−x2k+1)2t(1+x+x2+x3+...)2t
前一个因式的系数可以使用二项式定理求解,而第二个因式的系数可以使用组合数学意义来求解。设后一个因式可能达到的次数为g,那么 xg 的系数就是求 x1+x2+x3+...+x2t=g 且 xi≥0 的方程解的个数,按照组合数学的理论,很显然就是 C(gg+2t−1) 。
最后,在写法上可以按 0 、 2k+1 、 2(2k+1) 、。。。这样枚举前一个因式的指数,将区间减去这个指数(当区间完全到了负半轴时,枚举结束),然后求解此时区间在第二个因式范围内所有象的系数和,当然不要忘记乘以此时第一个因式的系数。在求第二因式的区间系数和的时候,不需要一个一个求了,可以使用前缀和。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define maxn 401000+10
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define pii pair
#define mp make_pair
#define FI first
#define SE second
#define IT iterator
#define PB push_back
#define Times 10
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const double eps = 1e-10;
const double pi = acos(-1.0);
const ll mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const ll INF = (ll)1e18+300;
const double delta = 0.98;
ll fac[maxn];
ll inv[maxn];
ll dat[maxn];
ll qlow(ll a,ll n){
ll ans=1;
while(n){
if(n&1)ans=(ans*a)%mod;
a=(a*a)%mod;
n>>=1;
}
return ans;
}
void init(){
inv[0]=fac[0]=1;
for(int i=1;i<=401000;i++){
fac[i]=fac[i-1]*i%mod;
inv[i]=qlow(fac[i],mod-2);
}
}
ll C(int n,int m){
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
int a,b,k,t;
init();
scanf("%d %d %d %d",&a,&b,&k,&t);
ll L=b-a+2*k*t,R=4*k*t;
if(L>R){
puts("0");
return 0;
}
dat[0]=1;
for(int i=1;i<=4*k*t;i++)
dat[i]=(dat[i-1]+C(i+2*t-1,i))%mod;
ll ans=0;
for(int i=0;1;i++){
if(i){
L-=(2*k+1);
R-=(2*k+1);
}
if(R<0)break;
int op=(i&1)?-1:1;
if(L<0)ans=(ans+op*C(2*t,i)*dat[R]%mod+mod)%mod;
else ans=(ans+op*C(2*t,i)*(dat[R]-dat[L])%mod+mod)%mod;
}
cout<return 0;
}
E. Memory and Casinos
分析:这道题也很难,但是主要是难在想法上,写法并不难。
题意:有n个赌场,排成一排,顺次编号。在第i家赌场赢的概率是 pi ,当你赢的时候就会离开第i家赌场,来到第i+1家赌场(或者你在第n家赌场赢了就会离开这个赌场游戏);当你输的时候也会离开第i家赌场,来到i-1家赌场(或者你在第1家赌场输了也会离开这个赌场游戏)。有两种操作:第一种,将第i家赌场赢的概率修改成 ai ;第二种操作,假如我进入第l家赌场赌钱,最后以赢的身份离开第r家赌场的概率是多少?
分析:这里是用线段树,至于为什么,可能是因为多组修改和查询,也可能是数据的不大不小,更多可能是一种灵光一闪。
按照线段树的角度来看,那么叶子结点存的肯定是在这家赌场赢的概率,然后推广到线段,知道分支结点存的是从左边L进入、以赢的身份从R出去的概率,不妨设为 L[l,r] 。那么,很显然,从左边L进入、以输的身份从L出去的概率为1- L[l,r] 。然后考虑怎么将小的区间合并成大的区间。我们假设已知 L[l,m] 和 L[m+1,r] 的所有属性(为了简便,使得 L[l,m]=l1 , L[m+1,r]=l2 ),要求 L[l,r] 的所有属性,我们考虑中间从m到m+1通过了几次
l1∗l2l1∗(1−l2)∗?∗l2
我们发现出了问题,?无法用数学语言表示,?表示的是从右边r进入、以赢的身份从r出去的概率。最简单的办法,就是继续设,设它为 R[l,r] 。和上面一样,已知了 R[l,m] 和 R[m+1,r] (同样假设, R[l,m]=r1 , R[m+1,r]=r2 ),求解 R[l,r] 。
这样先写出前面的概率式
l1∗l2l1∗(1−l2)∗r1∗l2l1∗((1−l2)∗r1)2∗l2......
按照等比数列求和法得到总概率是 l1∗l21−(1−l2)∗r1
这样L部分就结束了,还得考虑R部分
同样考虑通过从m+1到m通过的次数
r2(1−r2)∗r1∗l2(1−r2)∗r1∗(1−l2)∗r1∗l2(1−r2)∗r1∗((1−l2)∗r1)2∗l2......
按照等比数列求和法得到总概率是 r2+(1−r2)∗r1∗l21−(1−l2)∗r1
之后按照线段区间合并的写法,随便写写就可以了。
By shengtao96, contest: Codeforces Round #370 (Div. 2), problem: (E) Memory and Casinos, Accepted, #
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define maxn 100000+10
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define pii pair
#define mp make_pair
#define FI first
#define SE second
#define IT iterator
#define PB push_back
#define Times 10
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const double eps = 1e-10;
const double pi = acos(-1.0);
const ll mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const ll INF = (ll)1e18+300;
const double delta = 0.98;
double L[maxn<<2];
double R[maxn<<2];
void pushup(int rt){
L[rt]=L[rt<<1]*L[rt<<1|1]/(1-(1-L[rt<<1|1])*R[rt<<1]);
R[rt]=R[rt<<1|1]+(1-R[rt<<1|1])*R[rt<<1]*L[rt<<1|1]/(1-(1-L[rt<<1|1])*R[rt<<1]);
}
void build(int l,int r,int rt){
if(l==r){
int a,b;
scanf("%d %d",&a,&b);
L[rt]=R[rt]=1.0*a/b;
return ;
}
int m=(l+r)>>1;
build(lson);
build(rson);
pushup(rt);
}
void update(int l,int r,int rt,int p,double x){
if(l==r){
L[rt]=R[rt]=x;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(lson,p,x);
else update(rson,p,x);
pushup(rt);
}
double query(int l,int r,int rt,int LL,int RR,double& y){
query(rson,LL,RR,y);
else if(m>=RR)return query(lson,LL,RR,y);
else {
double r1,r2,l1,l2;
l1=query(lson,LL,RR,r1);
l2=query(rson,LL,RR,r2);
y=r2+(1-r2)*r1*l2/(1-(1-l2)*r1);
return l1*l2/(1-(1-l2)*r1);
}
}
int main(){
int n,q;
scanf("%d %d",&n,&q);
build(1,n,1);
while(q--){
int op,a,b,k;
scanf("%d",&op);
if(op==1){
scanf("%d %d %d",&k,&a,&b);
update(1,n,1,k,1.0*a/b);
}
else{
scanf("%d %d",&a,&b);
double y;
printf("%.10f\n",query(1,n,1,a,b,y));
}
}
return 0;
}
这篇题解有点晚,但希望对读者有所帮助吧!