这场简单,赛时莽过了前五个就下班了,结果发现F是个简单隔板法,亏大了。
D是模拟链表,E是变种01背包,F是概率论隔板法,数论的范畴。
A 小红小紫替换
思路:
如果是这个串是kou
就改成yukari
,没什么好说的。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
string s;
int main(){
cin>>s;
if(s=="kou")cout<<"yukari";
else cout<<s;
return 0;
}
B 小红的因子数
思路:
分解质因数,然后统计有多少种质因数。相当板子,考虑开方暴力从小到大枚举所有 ≤ x \le \sqrt{x} ≤x 可能的因子,每次遇到一个就从x中除去,每次遇到一个新的因子就计数值加一,这样跑一边就可以把所有 ≤ x \le \sqrt{x} ≤x 的质因子去掉了,最后再看一下x是不是大于1,是的话说明留下了一个 > x \gt \sqrt{x} >x 的质因子,计数值加一。
为什么不用判断素性:因为合数一定是由质数组成的,这里遇到质数就除掉了,所以自然 看到合数之前它就被破坏掉了。
为什么x大于1说明留下了一个 > x \gt \sqrt{x} >x 的质因子:因为 > x \gt \sqrt{x} >x 的质因子肯定没有两个,否则光它俩乘积就大于x了,所以剩下的肯定就一个。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
ll x;
int cnt;
int main(){
cin>>x;
for(ll i=2;i*i<=x;i++){
if(x%i==0){
while(x%i==0)x/=i;
cnt++;
}
}
if(x>1)cnt++;
cout<<cnt;
return 0;
}
C 小红的字符串中值
思路:
先从字符串中找到这个字符,然后以这个字符为中心,向两边扩展,得到长度为1,3,5…的串,这些串都是满足条件的,直到一边到达边界。其实就是比较一下字符串内每个字符是否等于给定字符,如果是累计一下答案。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
int n;
char t;
string s;
ll ans;
int main(){
cin>>n>>t>>s;
for(int i=0;i<n;i++){
if(t==s[i]){
ans+=min(i+1,n-i);
}
}
cout<<ans;
return 0;
}
D 小红数组操作
思路:
把一个数看作一个节点,其实这就是个模拟链表题,不过数的范围很大,而n的范围可以接受,所以可以离散化。也可以使用map。
code:
离散化:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;
int q;
int a[maxn<<1],cnt;//离散化数组
int nxt[maxn],pre[maxn];
struct operat{
int op,x,y;
}opt[maxn];
int main(){
cin>>q;
for(int i=1;i<=q;i++){
cin>>opt[i].op;
if(opt[i].op==1){
cin>>opt[i].x>>opt[i].y;
a[++cnt]=opt[i].x;
a[++cnt]=opt[i].y;
}
else {
cin>>opt[i].x;
a[++cnt]=opt[i].x;
}
}
sort(a+1,a+cnt+1);
cnt=unique(a+1,a+cnt+1)-a-1;
int sz=0;
nxt[1]=pre[1]=1;//0被分配到位置1,0作为头节点不存储信息
for(int i=1,x,y;i<=q;i++){
if(opt[i].op==1){
x=lower_bound(a+1,a+cnt+1,opt[i].x)-a;
y=lower_bound(a+1,a+cnt+1,opt[i].y)-a;
pre[x]=y;
nxt[x]=nxt[y];
pre[nxt[y]]=x;
nxt[y]=x;
sz++;
}
else {
x=lower_bound(a+1,a+cnt+1,opt[i].x)-a;
pre[nxt[x]]=pre[x];
nxt[pre[x]]=nxt[x];
sz--;
}
}
cout<<sz<<endl;
for(int i=nxt[1];i!=1;i=nxt[i])
cout<<a[i]<<" ";
return 0;
}
map:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
const int maxn=2e5+5;
int q;
map<int,int> nxt,pre;
int main(){
cin>>q;
nxt[0]=pre[0]=0;
for(int i=1,op,x,y;i<=q;i++){
cin>>op;
if(op==1){
cin>>x>>y;
pre[x]=y;
nxt[x]=nxt[y];
pre[nxt[y]]=x;
nxt[y]=x;
}
else {
cin>>x;
pre[nxt[x]]=pre[x];
nxt[pre[x]]=nxt[x];
nxt.erase(x);
pre.erase(x);
}
}
cout<<nxt.size()-1<<endl;
for(int i=nxt[0];i;i=nxt[i])
cout<<i<<" ";
return 0;
}
E 小红的子集取反
思路1:
变种01背包。题解的做法更漂亮,不过本质是一样的。先说说我的。
如果计算一下所有元素之和为 t o t tot tot,那么我们的任务其实就是要求选择最少的数,使得它们之和为 t o t / 2 tot/2 tot/2,这样的话,把选中的这些数变成负数就可以使得它们的和为0了,这样,很容易看出如果tot为奇数则肯定无解。而选择一些元素,每个元素最多选一次,元素和小于等于一个值,这就是01背包啊。不过因为有的元素可能是负数,所以是变种01背包。
如果只有正数,那么就是一个很板子的01背包,考虑如何处理负数。为了防止tot本身就是负的,如果tot为负数,tot和所有数就都取负。对数进行降序排序,先处理正数,再处理负数,这样可以防止访问到负的下标。容量记录到它们的绝对值之和,这样可以记录到所有状态。
01背包可以用二维,也可以用一维。一维注意枚举顺序。
code1:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=205;
const int inf=1e9;
int n,a[maxn],tot;
int dp[maxn*maxn*10];//前i个元素
int main(){
cin>>n;
int maxx=0;//绝对值之和
for(int i=1;i<=n;i++){
cin>>a[i];
tot+=a[i];
if(a[i]>=0)maxx+=a[i];
else maxx-=a[i];
}
if(tot<0){
for(int i=1;i<=n;i++)
a[i]=-a[i];
tot=-tot;
}
if(tot&1){
puts("-1");
return 0;
}
sort(a+1,a+n+1,greater<int>());
for(int j=0;j<=maxx;j++)
dp[j]=inf;
dp[0]=0;
for(int i=1;i<=n;i++){
if(a[i]>=0)
for(int j=maxx;j>=0;j--)
dp[j]=min(dp[j],(j-a[i]>=0)?dp[j-a[i]]+1:inf);
else
for(int j=0;j<=maxx;j++)
dp[j]=min(dp[j],(j-a[i]<=maxx)?dp[j-a[i]]+1:inf);
}
if(dp[tot>>1]!=inf)printf("%d",dp[tot>>1]);
else puts("-1");
return 0;
}
参考一下题解的部分处理,既然我们这么怕访问到负的下标,那么我们一开始就就给所有下标加一个偏移量不就行了。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=205;
const int inf=1e9;
int n,a[maxn],tot;
int dp[maxn*maxn*10];//前i个元素
int u=40000;
int main(){
cin>>n;
int maxx=u;//绝对值之和
for(int i=1;i<=n;i++){
cin>>a[i];
tot+=a[i];
if(a[i]>=0)maxx+=a[i];
else maxx-=a[i];
}
if(tot&1){
puts("-1");
return 0;
}
for(int j=0;j<=maxx;j++)
dp[j]=inf;
dp[u]=0;
for(int i=1;i<=n;i++){
if(a[i]>=0)
for(int j=maxx;j>=0;j--)
dp[j]=min(dp[j],(j-a[i]>=0)?dp[j-a[i]]+1:inf);
else
for(int j=0;j<=maxx;j++)
dp[j]=min(dp[j],(j-a[i]<=maxx)?dp[j-a[i]]+1:inf);
}
if(dp[u+tot/2]!=inf)printf("%d",dp[u+tot/2]);
else puts("-1");
return 0;
}
思路2:
一个元素要么加进来,要么减进来。其实就是要求所有数和起来等于0 废话 ,设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示前
i
i
i 个数加减起来等于
j
j
j 的 减的最少的次数。其实就是要求
d
p
[
n
]
[
0
]
dp[n][0]
dp[n][0],再配合偏移量,其实就是求
d
p
[
n
]
[
u
]
dp[n][u]
dp[n][u]。这就是题解的做法。
对一个 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j],尝试加入 a i a_i ai,它可以直接加进来,推到 d p [ i ] [ j + a i ] dp[i][j+a_i] dp[i][j+ai],也可以减进来,推到 d p [ i ] [ j − a i ] + 1 dp[i][j-a_i]+1 dp[i][j−ai]+1,初始 d p [ 0 ] [ u ] = 0 dp[0][u]=0 dp[0][u]=0,最终要求 d p [ n ] [ u ] dp[n][u] dp[n][u]。
其实可以边读边跑,可以省下一点空间。不过算了。
code2:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=205;
const int inf=1e9;
int n,a[maxn],tot;
int dp[maxn][maxn*maxn*2];//前i个元素
int u=40000;
int main(){
cin>>n;
int maxx=u;//绝对值之和
for(int i=1;i<=n;i++){
cin>>a[i];
tot+=a[i];
if(a[i]>=0)maxx+=a[i];
else maxx-=a[i];
}
if(tot&1){
puts("-1");
return 0;
}
memset(dp,0x3f,sizeof(dp));
dp[0][u]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=maxx;j++){
if(j+a[i]<=maxx)dp[i][j]=min(dp[i][j],dp[i-1][j+a[i]]);
if(j-a[i]>=0)dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]+1);
}
}
if(dp[n][u]!=0x3f3f3f3f)printf("%d",dp[n][u]);
else puts("-1");
return 0;
}
F 小红的连续段
思路:
开头先放连续的a或b,连续的a,然后再放连续的b,如此反复。问一共 i i i 段连续的方法数是什么。
因为ab是交替的,所以如果一共有 i i i 段,如果 i i i 是奇数,那么奇数段上的有 i + 1 2 \frac {i+1} 2 2i+1 段,偶数段上的有 i − 1 2 \frac {i-1} 2 2i−1 段。如果 i i i 是偶数,奇偶数段都是 i 2 \frac {i} 2 2i 段。a放在奇数段b放在偶数段的方法数 与 b放在奇数段a放在偶数段的方法数 之和就是答案。
现在只要研究某个字符放在若干段上的方法数就可以了。这个字符之间是相同的,而这些段之间是不同的(有顺序),每段都不为空。因此这其实就是球相同,盒不同,盒不为空的球盒问题。考虑隔板法。
其实很好理解,假设有n个球,m个盒。如果把球排成一排,n个球会有n-1个间隔,如果在n-1个间隔中插入m-1个隔板,就可以把这一排球分成m个不空的段,按顺序把每段放进盒子里即可。隔板的选择方式有 C n − 1 m − 1 C_{n-1}^{m-1} Cn−1m−1 种,这就说明n个相同球,m个不同盒,盒不为空的方法数为 C n − 1 m − 1 C_{n-1}^{m-1} Cn−1m−1。
那么上面的就好算了。假设
i
i
i 是奇数,答案数就是a放在奇数段b放在偶数段的方法数 与 b放在奇数段a放在偶数段的方法数 之和,即:
C
x
−
1
i
+
1
2
−
1
∗
C
y
−
1
i
−
1
2
−
1
+
C
y
−
1
i
+
1
2
−
1
∗
C
x
−
1
i
−
1
2
−
1
C^{\frac {i+1} 2-1}_{x-1}*C^{\frac {i-1} 2-1}_{y-1}+C^{\frac {i+1} 2-1}_{y-1}*C^{\frac {i-1} 2-1}_{x-1}
Cx−12i+1−1∗Cy−12i−1−1+Cy−12i+1−1∗Cx−12i−1−1
假设
i
i
i 是偶数,答案数就是:
C
x
−
1
i
2
−
1
∗
C
y
−
1
i
2
−
1
+
C
y
−
1
i
2
−
1
∗
C
x
−
1
i
2
−
1
C^{\frac {i} 2-1}_{x-1}*C^{\frac {i} 2-1}_{y-1}+C^{\frac {i} 2-1}_{y-1}*C^{\frac {i} 2-1}_{x-1}
Cx−12i−1∗Cy−12i−1+Cy−12i−1∗Cx−12i−1
预处理
C
x
−
1
i
C_{x-1}^{i}
Cx−1i 和
C
y
−
1
i
C_{y-1}^{i}
Cy−1i 即可。
code:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1005;
const ll mod=1e9+7;
int x,y;
ll Cx[maxn],Cy[maxn];
ll qpow(ll a,ll b){
ll base=a,ans=1;
while(b){
if(b&1){
ans*=base;
ans%=mod;
}
base*=base;
base%=mod;
b>>=1;
}
return ans;
}
ll inv(ll x){return qpow(x,mod-2);}
int main(){
cin>>x>>y;
Cx[0]=Cy[0]=1;
for(int i=1;i<x;i++)
Cx[i]=Cx[i-1]*(x-i)%mod*inv(i)%mod;
for(int i=1;i<y;i++)
Cy[i]=Cy[i-1]*(y-i)%mod*inv(i)%mod;
for(int i=1;i<=x+y;i++){
ll t=0,m1,m2;
if(i&1){
m1=(i+1)>>1;
m2=(i-1)>>1;
}
else m1=m2=i>>1;
// cout<<m1<<" "<<m2<<endl;
t=((m1>=1 && m1<=x)?Cx[m1-1]:0)*((m2>=1 && m2<=y)?Cy[m2-1]:0)%mod;
t=(t+((m2>=1 && m2<=x)?Cx[m2-1]:0)*((m1>=1 && m1<=y)?Cy[m1-1]:0))%mod;
cout<<t<<endl;
}
return 0;
}