杂题选做
都是思博题,全是思维没有技巧
P4006 小 Y 和二叉树
分析
首先可以贪心地确定第一个数,即度数
假设这个节点为
先讨论
建立一颗新的树,假设
在原树上,
而
再讨论度数为
总的来说,就是先将
- 如果当前节点是先序遍历,那么其左儿子为中序遍历,右儿子为先序遍历
- 如果当前节点是中序遍历,那么其左右儿子都是中序遍历
接下来再来考虑如何使字典序最小
先以
若新树上
若新树上
若新树上
若新树上
#include<bits/stdc++.h>
#define inf INT_MAX
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
return x*f;
}
int n,in[1000005],rt,f[1000005];
vector<int>e[1000005];
void dp(int u,int fa){
f[u]=(in[u]<=2?u:inf);
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(v^fa)dp(v,u),f[u]=min(f[u],f[v]);
}
}
void dfs(int u,int fa,int op){//op=1:中序,op=0:先序
vector<int>son;
for(int i=0;i<e[u].size();i++){
if(e[u][i]^fa)son.push_back(e[u][i]);
}
if(!op){//先序
printf("%d ",u);
if(son.size()==1){
if(f[son[0]]<son[0])dfs(son[0],u,1);//中序遍历
else dfs(son[0],u,0);
}else if(son.size()==2){
if(f[son[0]]<f[son[1]]){
dfs(son[0],u,1);
dfs(son[1],u,0);
}else{
dfs(son[1],u,1);
dfs(son[0],u,0);
}
}
}else{//中序
if(son.size()==0)printf("%d ",u);
else if(son.size()==1){
if(f[son[0]]<u){
dfs(son[0],u,1);
printf("%d ",u);
}else{
printf("%d ",u);
dfs(son[0],u,1);
}
}else if(son.size()==2){
if(f[son[0]]<f[son[1]]){
dfs(son[0],u,1);
printf("%d ",u);
dfs(son[1],u,1);
}else{
dfs(son[1],u,1);
printf("%d ",u);
dfs(son[0],u,1);
}
}
}
}
int main(){
n=read();
for(int i=1;i<=n;i++){
in[i]=read();
for(int j=1,x;j<=in[i];j++){
x=read();
e[i].push_back(x);
}
if(!rt&&in[i]<=2)rt=i;
}
dp(rt,0),dfs(rt,0,0);
puts("");
return 0;
}
ARC066E Addition and Subtraction Hard
分析
贪心考虑如何加括号时最优
发现一个左括号在减号之后才有意义,而在当前括号后的第一个左括号之前的数都是产生负贡献的,第二个括号以后的数都能产生正贡献:括号后所有数取反,即要让括号中的总和最小,发现括号中还有加号的情况,只需要跟上一个减号括在一起变成负数即可
故枚举负号位置,即第一个大括号的位置,计算贡献即可
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,num[100005],sum[100005],ne[100005],sum_po[100005],cnt,res;
char c[100005];
signed main(){
cin>>n;
c[0]='+';
for(int i=1;i<=n;i++){
cin>>num[i];
if(i^n)cin>>c[i];
if(c[i-1]=='+')sum[i]=sum[i-1]+num[i];//没有更改符号时的前缀和
else ne[++cnt]=i,sum[i]=sum[i-1]-num[i];
sum_po[i]=sum_po[i-1]+num[i];//全部取正数的前缀和
}
res=sum[n],ne[cnt+1]=n;//ne[]记录负号位置
for(int i=1;i<=cnt;i++){//枚举负号位置
int ans1=sum[ne[i]];//当前负号之前的所有数之和
int ans2=sum[ne[i+1]-1]-sum[ne[i]];//当前负号到下一个负号之间的和
int ans3=sum_po[n]-sum_po[ne[i+1]-1];//下一个负号后均产生正贡献
res=max(res,ans1-ans2+ans3);
}
cout<<res<<endl;
return 0;
}
P3802 小魔女帕琪
分析
好难,个人认为难度高于绿
首先先考虑施法
假设第一个数已定,设为
则第一个数已定的情况下,成功释放七重奏的概率为:
这种排列有
再考虑施法
由于魔法发动互不影响,故概率为:
设
发现与施法
在第任意次触发一个七重奏的概率与在第
而能触发七重奏的最多次数应该为
所以答案为:
#include<bits/stdc++.h>
#define inf 1000000000
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int a[10],sum;
double ans=1;
int main(){
for(int i=1;i<=7;i++)a[i]=read(),sum+=a[i];
if(sum<7)puts("0.000");
else{
for(int i=1;i<=7;i++)ans=ans*1.0*a[i]/(sum-i+1);
printf("%.3lf\n",ans*1*2*3*4*5*6*7*(sum-6));
}
return 0;
}
AGC008D K-th K
分析
对于第
-
1$ 至 $X_i-1$ 中有 $i-1$ 个 $i -
X_i+1$ 至 $n^2$ 中有 $n-i$ 个 $i
故有一个构造方法为:每次贪心选
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,pos[505],vis[505*505],num[505*505],ans[505*505];
int main(){
n=read();
for(int i=1;i<=n;i++){
pos[i]=read();
if(vis[pos[i]]){puts("No");return 0;}
vis[i]=1,num[pos[i]]=ans[pos[i]]=i;
}
int p=1;
for(int i=1;i<=n*n;i++){
if(num[i]){
for(int j=1;j<=num[i]-1;j++){
while(ans[p])++p;
if(p>i){puts("No");return 0;}
ans[p]=ans[i];
}
}
}
p=n*n;
for(int i=n*n;i;i--){
if(num[i]){
for(int j=1;j<=n-num[i];j++){
while(ans[p])--p;
if(i>p){puts("No");return 0;}
ans[p]=ans[i];
}
}
}
puts("Yes");
for(int i=1;i<=n*n;i++)printf("%d ",ans[i]);
puts("");
return 0;
}
AGC006D Median Pyramid Hard
分析
由于转移的条件是中位数,所以只关系数之间的大小关系
很典地将序列变成 01 串,大于中位数的是 1,否则是 0
手推发现两个相同的相邻数会一起往上走
所以只需要找到离中间最近的一组相邻一样的两个数,就可以得到最上面是什么数
由于序列长度为奇数,故不存在两组距离相同的相邻数
注意需要特判没有相邻相同数的情况
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,a[200005];
inline bool check(const int &k){
for(int i=0;i<n-1;i++){
if((a[n+i]<=k&&a[n+i+1]<=k)||(a[n-i]<=k&&a[n-i-1]<=k))return 1;
else if((a[n+i]>k&&a[n+i+1]>k)||(a[n-i]>k&&a[n-i-1]>k))return 0;
}
return a[1]<=k;
}
int main(){
n=read();
for(int i=1;i<=2*n-1;i++)a[i]=read();
int l=1,r=2*n-1;
while(l<r){
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}