Educational Codeforces Round 81 (Rated for Div. 2)
A. Display The Number
题解
题目要求数越大越好,并且有暗示答案会爆longlong,
于是我们就有如下策略:
\(n\) 是偶数全填\(1\) ,因为\(1\) 只占两格,很值得
\(n\) 是奇数的话先狂填\(1\) ,再还剩下\(3\) 个的时候填一个\(7\) 就好了
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
int w[10]={6,2,5,5,4,5,6,3,7,6};
int T,n;
int main()
{
T=read();
while(T--)
{
n=read();
if(n%2==0)
for(int i=1;i<=n/2;i++)printf("1");
else
{
printf("7");
for(int i=1;i<n/2;i++)printf("1");
}
printf("\n");
}
return 0;
}
B. Infinite Prefixes
题解
破题卡了好久,觉得语言难以描述怎么解
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int maxn=100010;
int T,n,x,p0[maxn],p1[maxn],pre[maxn],MAX;
char s[maxn];
int main()
{
T=read();
while(T--)
{
mem(p0,0);mem(p1,0);mem(pre,0);
n=read();x=read();
int cnt=0;
scanf("%s",s+1);
for(int i=1;i<=n;i++)p0[i]=p0[i-1]+(s[i]=='0'),p1[i]=p1[i-1]+(s[i]=='1'),pre[i]=p0[i]-p1[i];
if(x==0)cnt=1;
if(pre[n])
{
for(int i=1;i<=n;i++)if((x-pre[i])%pre[n]==0 && (x-pre[i])/pre[n]>=0)cnt++;
}
else
{
for(int i=0;i<=n;i++)if(x==pre[i])cnt++;
}
if(!pre[n] && cnt)printf("-1\n");
else printf("%d\n",cnt);
}
return 0;
}
C. Obtain The String
题解
特别像字符串匹配
但是我没这么做,对模板串预处理,省得匹配串耽误时间。
设\(pre[i]\) 表示字母\(i\) 的最早出现位置,\(suf[i][j]\) 表示表示在第\(i\) 位字符后,字母\(j\) 最早出现位置,这个可以用\(O(26n)\) 预处理出来
然后就直接在上面左右反复横跳就可以了
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int maxl=100010;
int T;
char s[maxl],t[maxl];
int pre[30],suf[maxl][30];//pre(i)表示字符i最早出现位置 ,suf(i,j)表示在i字符后,字符j最早出现位置
int main()
{
T=read();
while(T--)
{
mem(pre,42);mem(suf,42);
scanf("%s%s",s,t);
int l1=strlen(s),l2=strlen(t),cnt=0,ok=0;
for(int i=0;i<l1;i++)pre[s[i]-'a']=min(pre[s[i]-'a'],i);
for(int i=0;i<l1-1;i++)suf[i][s[i+1]-'a']=i+1;
for(int i=l1-1;i>=0;i--)
for(int j=0;j<26;j++)suf[i][j]=min(suf[i][j],suf[i+1][j]);
for(int i=0;i<l2;)
{
++cnt;
int pos=pre[t[i]-'a'];i++;
if(pos>=l1){ok=1;break;}
while(suf[pos][t[i]-'a']<l1)pos=suf[pos][t[i]-'a'],i++;
}
if(!ok)printf("%d\n",cnt);
else printf("%d\n",-1);
}
return 0;
}
D. Same GCDs
题解
设\(gcd(a,m)=x\) ,有\(a=k_1x,m=k_2x\)
又知道\(gcd(a+b,m)=x\),有\(a+b=k_3x\)
现在问题就变成了,在闭区间内\([k_1,k_1+k_2-1]\) 内有多少个与\(k_2\) 互质
拆成前缀问题 ,对\(n\) 分解质因数后对因子容斥原理即可
有一道类似的题 这是链接
一觉起来发现自己蠢死了,\(gcd(a+x,m)=gcd((a+x)%m,m)\) 所以 $\sum^{k_1+k_2-1}_{k_1}[gcd(i,k2)=1] $ 直接就变成了\(\phi(k2)\)
这样的话代码还能少些好几行
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline LL read()
{
LL x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
int T;
LL a,m,x,k1,k2;
LL gcd(LL a,LL b){return b==0 ? a : gcd(b,a%b);}
#include<vector>
vector<LL> pme;
LL count_prime(LL x,LL n){
pme.clear();
LL i,j;
for(i=2;i*i<=n;i++)
if(n%i==0){
pme.push_back(i);
while(n%i==0)n/=i;
}
if(n>1)pme.push_back(n);
LL sum=0,value,cnt;
for(i=1;i<(1<<pme.size());i++){
value=1;
cnt=0;
for(j=0;j<pme.size();j++){
if(i&(1<<j)){
value*=pme[j];
cnt++;
}
}
if(cnt&1)
sum+=x/value;
else sum-=x/value;
}
return x-sum;
}
int main()
{
T=(int)read();
while(T--)
{
a=read();m=read();
x=gcd(a,m);
k1=a/x;k2=m/x;
cout<<count_prime(k2+k1-1,k2)-count_prime(k1-1,k2)<<endl;
}
return 0;
}
事后补题
E. Permutation Separation
题解
考试的时候没仔细想
翻译题目条件,那个左右集合的限制的翻译过来就是存在一个值\(val\) ,左边都比\(val\) 小,右边\(val\) ,对于在\(pos\) 位置进行前后切割的方案,计算的答案是\(\sum^{pos}_{i=1}[p_i>val]a_i+\sum^n_{i=pos+1}[p_i<val]a_i\)
看到了这个式子思路就很清晰了。我们选择枚举\(val\) ,当\(val\) 依次递增时,分析一波都有哪些位置的答案有了变化(此处省略**字),用一个支持区间加法,区间取min的线段树维护这些答案即可。
复杂度\(O(nlogn)\)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int maxn=200010;
int n,p[maxn],pos[maxn],a[maxn];
LL minv[maxn<<2],addv[maxn<<2],A[maxn];
void build(int o,int L,int R)
{
if(L==R){minv[o]=A[L];return;}
int mid=L+R>>1,lo=o<<1,ro=lo|1;
build(lo,L,mid);build(ro,mid+1,R);
minv[o]=min(minv[lo],minv[ro]);
}
void pushdown(int o,int L,int R)
{
int mid=L+R>>1,lo=o<<1,ro=lo|1;
if(L==R){addv[o]=0;return;}
addv[lo]+=addv[o];addv[ro]+=addv[o];
minv[lo]+=addv[o];minv[ro]+=addv[o];
addv[o]=0;
}
void update(int o,int L,int R,int ql,int qr,int c)
{
if(ql>qr)return;
// printf("%d %d %d %d %d\n",o,L,R,ql,qr);
if(L==ql && R==qr)
{
addv[o]+=c;
minv[o]+=c;
return;
}
pushdown(o,L,R);
int mid=L+R>>1,lo=o<<1,ro=lo|1;
if(qr<=mid)update(lo,L,mid,ql,qr,c);
else if(ql>mid)update(ro,mid+1,R,ql,qr,c);
else update(lo,L,mid,ql,mid,c),update(ro,mid+1,R,mid+1,qr,c);
minv[o]=min(minv[lo],minv[ro]);
}
LL query(int o,int L,int R,int ql,int qr)
{
if(L==ql && R==qr)return minv[o];
pushdown(o,L,R);
int mid=L+R>>1,lo=o<<1,ro=lo|1;
LL res;
if(qr<=mid)res=query(lo,L,mid,ql,qr);
else if(ql>mid)res=query(ro,mid+1,R,ql,qr);
else res=min(query(lo,L,mid,ql,mid),query(ro,mid+1,R,mid+1,qr));
minv[o]=min(minv[lo],minv[ro]);
return res;
}
LL ans,sum1,sum2;
int main()
{
n=read();
for(int i=1;i<=n;i++)p[i]=read(),pos[p[i]]=i;
for(int i=1;i<=n;i++)a[i]=read(),A[i]=A[i-1]+a[i];
build(1,1,n);
ans=query(1,1,n,1,n-1);
for(int i=1;i<=n;i++)
{
update(1,1,n,1,pos[i]-1,a[pos[i]]);
update(1,1,n,pos[i],n,-a[pos[i]]);
ans=min(ans,query(1,1,n,1,n-1));
}
cout<<ans<<endl;
return 0;
}
/*
6
3 5 1 6 2 4
9 1 9 9 1 9
*/
F. Good Contest
题解
学长所谓的很朴素的思路[微笑脸]
两种方式,一种是概率DP,还有一种是统计合法方案数最后除以总方案数
前者做法好像标程是用多项式
后者的话,高人们想出了更好的做法。
一个十分正常的\(dp\) 。\(dp[i][j]\) 表示现在拍到\(i\) ,并且第\(i\) 场考试的人A题数为\(j\) 的合法方案数
转移的话因为\(j\) 的取值太大,所以复杂度无法承受,尝试优化\(j\) 这个东西。
把\(n\) 个区间端点离散化\([L_i,R_i+1)\),这样就把整个数轴划分成了若干个不可分的区间线段,这样就把\(j\) 由具体数值转化为了区间序号,复杂度直接就降了下来。
继续设\(dp[i][j]\) 表示现在排到\(i\) ,且之前的考试全都安排在了\(j\) 以及\(j\) 之后的区间里的方案数。
转移的话,\(dp[i][j]\) 可由\(dp[k][l]\) 转移而来,其中\(j \geq L_{k+1} , j<R_{k+1}\) ,\(l>j\) 。意义是可以将\(k+1~i\) 这些场考试全安排在\(j\) 这个区间上,转移的时候需要乘以一个组合系数,这个系数的意思是表示在长为n的闭区间中,选k个数有重复且有序的方案,我的公式是\(\sum^{k}_{i=1} C^{i}_{n}C^{i-1}_{k-1}\) ,组合意义是从\(n\) 个数中选\(i\) 个数,\(k\) 个数就由这\(i\) 种数组成 然后再插板 。但其实答案也可以简单归结为\(C^k_{n+k-1}\) 。这个组合意义我也不是很懂,据说是打表打出来的??
另外为什么一定要从\(l>j\) 转移而来,因为我们会发现\(l=j\) 是无法转移的,但是这样丝毫不影响我们计算最终答案。
整体复杂度\(O(n^4)\),这个\(n=50\) 给的可真好。
至于区间为什么要选取左闭右开的话,我猜这应该是一种常用技巧吧,要不然如果全选成左闭右闭的话,会在端点处出现一些麻烦。
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int MOD=998244353,maxn=55;
int quickpow(int a,int N)
{
int res=1,tmp=a;
while(N)
{
if(N&1)res=((LL)res*(LL)tmp)%MOD;
tmp=((LL)tmp*(LL)tmp)%MOD;
N>>=1;
}
return res;
}
int Inv(int a){return quickpow(a,MOD-2);}
int n,L[maxn],R[maxn],dp[maxn][maxn<<1],node[maxn<<1],inv[maxn],sum[maxn][maxn<<1],len,pro=1,ans;
int C(int x,int y)
{
int res=0,c=x,c2=1;
for(int i=1;i<=y;i++)
{
if(i!=1)c=(((LL)c*(LL)(x-i+1))%MOD*inv[i])%MOD,c2=(((LL)c2*(LL)(y-i+1))%MOD*inv[i-1])%MOD;
res=(res+((LL)c*(LL)c2)%MOD)%MOD;
}
return res;
}
int main()
{
n=read();
inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=((LL)(MOD-MOD/i)*(LL)inv[MOD%i])%MOD;
for(int i=1;i<=n;i++)L[i]=read(),R[i]=read()+1,pro=((LL)pro*(LL)(R[i]-L[i]))%MOD,node[++len]=L[i],node[++len]=R[i];
sort(node+1,node+len+1);
len=unique(node+1,node+len+1)-node-1;
for(int i=1;i<=n;i++)
{
L[i]=lower_bound(node+1,node+len+1,L[i])-node;
R[i]=lower_bound(node+1,node+len+1,R[i])-node;
}
for(int i=0;i<=len;i++)sum[0][i]=1;
for(int i=1;i<=n;i++)
{//dp[i][j]表示目前已经填了i个数,当前位置在区间j的方案数
for(int j=L[i];j<R[i];j++)
for(int k=i-1;k>=0 && j>=L[k+1] && j<R[k+1];k--)//可以将k+1~i次安排在这个区间上
dp[i][j]=(dp[i][j]+((LL)C(node[j+1]-node[j],i-k)*(LL)sum[k][j+1])%MOD)%MOD;//C(i,j)表示在长为i的闭区间中,选j个数有重复且有序的方案
for(int j=len;j>=0;j--)sum[i][j]=(sum[i][j+1]+dp[i][j])%MOD;
}
cout<<((LL)sum[n][0]*(LL)Inv(pro))%MOD<<endl;
return 0;
}
废话
脑子还是不好使,处理问题速度太慢啊
补题效率堪忧[微笑脸]