这场难度控制的特别出色,不难但是都很有意思,尤其是E这个构造部分。比赛链接,官方视频讲解。
AB没有用到什么算法,C是个字符串处理,D是中位数,E是构造,F是概率DP。
比大小,没什么好说的
#include
#include
using namespace std;
int main(){
int a,b;
cin>>a>>b;
puts((a==b)?"draw":(a>b)?"kou":"yukari");
return 0;
}
如果一天没有梦,那么这天就没有贡献,如果有一个,就一定放到白天,产生两点贡献,如果有两个,产生三点贡献。
#include
#include
#include
using namespace std;
int n,ans;
string a,b;
int main(){
cin>>n>>a>>b;
for(int i=0;i<n;i++){
if(a[i]=='Y' || b[i]=='Y')ans+=2;
if(a[i]=='Y' && b[i]=='Y')ans++;
}
cout<<ans<<endl;
return 0;
}
因为一定有“xiao”和“hong”这两个串,然后把它们取出来放在一起,其他的字符串不用管,所以可以用string。
string里有一个成员函数叫find(str),它可以从字符串中找到子串str,并返回它的首地址下标。然后还有一个成员函数erase(pos,len),它可以把字符串从pos下标位置开始后len个字符删掉。用这两个函数可以把“xiao”和“hong”这两个串剔出来,之后放到字符串末尾即可。
#include
#include
#include
using namespace std;
string a;
int main(){
cin>>a;
a.erase(a.find("xiao"),4);
a.erase(a.find("hong"),4);
a+="xiaohong";
cout<<a;
return 0;
}
中位数的定义是 一组数据中排在中间位置的数,如果数据量是偶数,则中位数是中间两个数的平均值。
所以可以分情况来看,先对数组排序,如果一开始n为偶数,中位数就是第 n 2 \frac n 2 2n 和 n 2 + 1 \frac n 2+1 2n+1 个数和的一半,去掉一个变成奇数。如果删掉的那个是前 n 2 \frac n 2 2n 的数,中位数相当于会右移一位,也就是原来的第 n 2 + 1 \frac n 2+1 2n+1 个,如果删掉的是后 n 2 \frac n 2 2n 的数,则为第 n 2 \frac n 2 2n 个。(其实说白了就是要找第 n 2 \frac {n} 2 2n 个元素,如果前面的数消失了,就顺位往后找一位即可。)
如果一开始n为奇数,中位数就是第 ⌈ n 2 ⌉ \left\lceil\frac n 2\right\rceil ⌈2n⌉ 个数,去掉一个变成偶数。如果删掉的那个是前 ⌊ n 2 ⌋ \left\lfloor\frac n 2\right\rfloor ⌊2n⌋ 的数,中位数是第 ⌊ n 2 ⌋ + 1 \left\lfloor\frac n 2\right\rfloor+1 ⌊2n⌋+1 和第 ⌊ n 2 ⌋ + 2 \left\lfloor\frac n 2\right\rfloor+2 ⌊2n⌋+2 个和的一半。如果删掉的是第 ⌈ n 2 ⌉ \left\lceil\frac n 2\right\rceil ⌈2n⌉,中位数是第 ⌊ n 2 ⌋ \left\lfloor\frac n 2\right\rfloor ⌊2n⌋ 和第 ⌊ n 2 ⌋ + 2 \left\lfloor\frac n 2\right\rfloor+2 ⌊2n⌋+2 个和的一半。如果删掉的是后 ⌈ n 2 ⌉ \left\lceil\frac n 2\right\rceil ⌈2n⌉的数,中位数是第 ⌊ n 2 ⌋ \left\lfloor\frac n 2\right\rfloor ⌊2n⌋ 和第 ⌊ n 2 ⌋ + 1 \left\lfloor\frac n 2\right\rfloor+1 ⌊2n⌋+1 个和的一半。(其实说白了就是要找第 ⌊ n 2 ⌋ \left\lfloor\frac {n} 2\right\rfloor ⌊2n⌋ 和 ⌊ n 2 ⌋ + 1 \left\lfloor\frac {n} 2\right\rfloor+1 ⌊2n⌋+1 个元素,如果第 ⌊ n 2 ⌋ \left\lfloor\frac {n} 2\right\rfloor ⌊2n⌋ 前面的数消失了,就两个数都顺位往后找一位即可,第 ⌊ n 2 ⌋ + 1 \left\lfloor\frac {n} 2\right\rfloor+1 ⌊2n⌋+1 个丢失了就这一个数向后找一位)
只看理论容易迷糊,用实例来手推的话写起来很快。
题解的思路是 对数组排好序后从小到大枚举每个元素,把答案和这个元素的原来下标绑定一下,根据原来下标排好序后依次输出答案即可。但是找中位数的思路和上面是一致的
#include
#include
#include
#include
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],b[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+n+1);
int tn;
double ans;
if(n&1){
for(int i=1;i<=n;i++){
tn=(n-1)/2;
if(a[i]<=b[tn])ans=(b[tn+1]+b[tn+2])/2.0;
else if(a[i]==b[tn+1])ans=(b[tn]+b[tn+2])/2.0;
else ans=(b[tn]+b[tn+1])/2.0;
printf("%.1lf\n",ans);
}
}
else {
for(int i=1;i<=n;i++){
tn=n/2;
if(a[i]<=b[tn])ans=b[tn+1];
else ans=b[tn];
printf("%.1lf\n",ans);
}
}
return 0;
}
有意思的来了。
首先需要先对x分解质因数,统计每个质因数和它的个数。然后用前面的质因数尝试构造出一个相邻两数不同的数列。分解质因数统计个数这一步可以用素数筛,也可以开方暴力,因为合数在分解之前,它的质因数一定会被分解到,所以开方暴力不需要判断素数,建议使用,很好写。
问题在于如何构造,因为 1 ≤ x ≤ 1 0 13 1\le x\le 10^{13} 1≤x≤1013 最多也就四五十个质因数,所以很暴力的做法都不会超时(别写dfs就行):
这种题有很多种解法,本人愚钝不能参悟,只能整理出这三种解法。万无禁忌,千变万化,这也是算法竞赛的诱人之处吧。
第一种思路,贪心,用的循环双链表,注意特判x=1。由于x可能有个大素数因子,所以要开longlong,否则会爆吃一串WA。
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e7+5;
const int maxm=10005;
ll x;
ll prime[maxn],counter;
bool vist[maxn];
void Eular(int n){
for(int i=2;i<=n;i++){
if(!vist[i])prime[++counter]=i;
for(int j=1,p=prime[1];j<=counter && i*p<=n;p=prime[++j]){
vist[i*p]=true;
if(i%p==0)break;
}
}
}
pair<ll,int> d[maxm];//质因数,个数
int ct,n;
int nxt[maxm],pre[maxn],cnt;
ll a[maxm];
int main(){
Eular(1e7);
cin>>x;
if(x==1){
printf("-1");
return 0;
}
for(int i=1,p=prime[1];1ll*p*p<=x;p=prime[++i]){
if(x%p==0){
d[++ct].first=p;
while(x%p==0){
d[ct].second++;
x/=p;
}
n+=d[ct].second;
}
}
if(x!=1){
d[++ct].first=x;
d[ct].second++;
n+=d[ct].second;
}
sort(d+1,d+ct+1,[](pair<ll,int> x,pair<ll,int> y){return x.second>y.second;});
int p=0;
for(int i=1;i<=ct;i++){
p=nxt[0];
while(p && a[p]!=a[nxt[p]])p=nxt[p];
for(int j=1;j<=d[i].second;j++){
a[++cnt]=d[i].first;//向p后插入
pre[cnt]=p;
nxt[cnt]=nxt[p];
pre[nxt[p]]=cnt;
nxt[p]=cnt;
p=nxt[cnt];
}
}
bool f=false;
for(int p=nxt[0];nxt[p];p=nxt[p]){
if(a[p]==a[nxt[p]]){
f=true;
break;
}
}
if(f){
printf("-1");
return 0;
}
printf("%lld\n",n);
for(int p=nxt[0];p;p=nxt[p]){
printf("%lld ",a[p]);
}
return 0;
}
第二种思路:
用堆来存储(题解用multiset,其实是一样的),每次取出最大和次大各输出一个,然后放回堆。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
ll x;
priority_queue<pair<int,ll> > h;//个数 质因数
int sum;
int main(){
cin>>x;
if(x==1){
printf("-1");
return 0;
}
for(ll i=2;1ll*i*i<=x;i++){
if(x%i==0){//这里不用判素数,因为组成合数的素数早就被除掉了
pair<int,ll> t(0,0);
t.second=i;
while(x%i==0){
t.first++;
x/=i;
}
sum+=t.first;
h.push(t);
}
}
if(x!=1){
sum++;
h.push(make_pair(1,x));
}
if(h.top().first>sum-h.top().first+1){
printf("-1");
return 0;
}
printf("%d\n",sum);
while(!h.empty()){
pair<int,ll> t1=h.top(),t2;
h.pop();
if(!h.empty()){
t2=h.top();
h.pop();
printf("%lld %lld ",t1.second,t2.second);
t1.first--;
t2.first--;
if(t1.first)h.push(t1);
if(t2.first)h.push(t2);
}
else {
printf("%lld\n",t1.second);//就剩一个了
break;
}
}
return 0;
}
第三种思路。
#include
#include
#include
#include
using namespace std;
const int maxn=105;
typedef long long ll;
ll x;
pair<int,ll> d[maxn];//个数 质因数
int cnt,sum;
ll a[maxn];
int main(){
cin>>x;
if(x==1){
printf("-1");
return 0;
}
for(ll i=2;1ll*i*i<=x;i++){
if(x%i==0){
d[++cnt].second=i;
while(x%i==0){
d[cnt].first++;
x/=i;
}
sum+=d[cnt].first;
}
}
if(x!=1){
sum++;
d[++cnt]=make_pair(1,x);
}
sort(d+1,d+cnt+1,greater<pair<int,ll> >());
if(d[1].first>sum-d[1].first+1){
printf("-1");
return 0;
}
printf("%d\n",sum);
int i=1;
for(int p=1;p<=sum;p+=2){
a[p]=d[i].second;
--d[i].first;
if(d[i].first==0)i++;
}
for(int p=2;p<=sum;p+=2){
a[p]=d[i].second;
--d[i].first;
if(d[i].first==0)i++;
}
for(int j=1;j<=sum;j++)
printf("%lld ",a[j]);
return 0;
}
虽说概率DP给人的感觉就是吓人,但是这个还挺好写的。
首先看到有两个技能,一个是随机一个石子堆石子-1,另一个技能是全体石子堆石子-1。而且题目说了每堆石子初始个数小于等于2。那么可以推出两个很显然的结论:
假设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示某一方 面对 一共 i i i 堆非空石子堆, j j j 堆为2个石子的石子堆 的局面的胜率。那么
由于 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示某一方的胜率,而游戏是两个玩家交替进行,我们在一个局面,它的下一个局面的胜率实际上是对手在面对,dp值是对手的胜率,也就是我们在这个状态的败率。因此从下一个状态推过来时,需要用败率来乘转移概率再求和。也就是 d p [ 当前状态 ] = ∑ ( 1 − d p [ 下一个状态 ] ) ∗ 转移概率 dp[当前状态] = \sum (1-dp[下一个状态])*转移概率 dp[当前状态]=∑(1−dp[下一个状态])∗转移概率
#include
#include
using namespace std;
const int maxn=1e4+5;
const int mod=1e9+7;
typedef long long ll;
int n,cnt1,cnt2;
ll dp[maxn][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;
}
inline ll inv(ll x){
return qpow(x,mod-2);
}
int main(){
cin>>n;
for(int i=1,t;i<=n;i++){
cin>>t;
if(t==1)cnt1++;
if(t==2)cnt2++;
}
for(int i=0;i<=n;i++){
for(int j=0;j<=min(i,cnt2);j++){
if(i>0)dp[i][j]+=(i-j)*inv(i)%mod*((1-dp[i-1][j]+mod)%mod)%mod;//拿石子1的堆
if(j>0)dp[i][j]+=j*inv(i)%mod*((1-dp[i][j-1]+mod)%mod)%mod;//拿石子2的堆
else dp[i][j]=1;
dp[i][j]%=mod;
}
}
cout<<dp[n][cnt2];
return 0;
}