The 2021 ICPC Asia Kunming Regional Contest
The 2021 ICPC Asia Kunming Regional Contest
D-Divisions
做法:我们发现,当构造成a a a b b b c c c \((a<b<c)\)这样的时候,答案就是\(2^{num(a)}-1+2^{num(b)}-1+2^{num(c)}-1+1\),我们选ac,只剩b时就可以,或者ab,只剩c,类似这样的选法,有\(2^{x}-1\)种,而最后可以全选再加一种,所以我们构造出来的序列方案数,就是上述式子求和,只需要从高位往下一直构造,肯定能构造任意的k,
点击查看代码
#include <bits/stdc++.h>
using namespace std;
bool check(int k){
int x=1;
while(x<=k){
if(x==k) return 1;
x=x*2;
}
return 0;
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int a[30];
for (int i=0;i<=30;i++) a[i]=(1<<i);
int k;
cin>>k;
vector<int>ans;
if(k==0){
printf("8\n");
printf("1 100 50 78 87 1 2 100");
return 0;
}
if(k==1){
printf("6\n");
printf("1 1 4 5 1 4");
return 0;
}
if(check(k)){
int p=log2(k);
for (int i=1;i<=p;i++) ans.push_back(1);
}else {
int top=1;
while(k>1){
for (int i=30;i>=0;i--){
if(k==1) break;
if(k>=a[i] && k-(a[i]-1)>=1){
k-=(a[i]-1);
for (int j=1;j<=i;j++) ans.push_back(top);
top++;
}
}
}
}
printf("%d\n",ans.size());
for (auto x:ans){
printf("%d ",x);
}
return 0;
}
E—Easy String Problem
做法:我们用[L,R]表示删除[L,R]区间后形成的序列,那么我们计算不同的序列个数就是\(L*(n-R+1)\)
但是会有重复的,对于同样长度的区间,如果[x,y]=[x-1,y-1],那么就意味着该区间只能算[x,y]一次,所以减去重复的就等价于\(L*(n-R+1)-[x,y]==[x-1,y-1]的区间的数量\) \((x<=L-1,y>=R-1)\),而出现这样的区间就等价于存在a[l-1]==a[r],所以我们需要统计的就是有多少对(p,q)满足\(a[p]==a[q]\)\((p<=L-1,q>=R+1)\),该题可以离线,所以我们可以莫队维护,在做莫队的时候一定要想清楚当前区间[l,r]的答案表示的是什么,赛上就是出现这个问题,Wa了无数发,
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
typedef long long ll;
int N;
struct my{
int l,r,id;
bool operator<(const my &rhs)const {
if(l/N!=rhs.l/N) return l<rhs.l;
return ( (l/N)&1 ) ? r < rhs.r : r > rhs.r;
}
};
my Q[maxn];
int a[maxn];
ll Ans[maxn];
ll ans=0,tot=0;
int pre[maxn],Next[maxn];
void delp(int x){
tot-=Next[x];
pre[x]--;
}
void deln(int x){
tot-=pre[x];
Next[x]--;
}
void addp(int x){
tot+=Next[x];
pre[x]++;
}
void addn(int x){
tot+=pre[x];
Next[x]++;
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int n;
scanf("%d",&n);
N=sqrt(n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int q;
scanf("%d",&q);
for (int i=1;i<=q;i++){
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+q);
int l=1,r=n;
for (int i=1;i<=q;i++){
int L=Q[i].l;
int R=Q[i].r;
while (l > L) delp(a[--l]);
while (r < R) deln(a[++r]);
while (l < L) addp(a[l++]);
while (r > R) addn(a[r--]);
int tot2=0;
Ans[Q[i].id]=(ll)(Q[i].l)*(ll)((ll)n-Q[i].r+1ll)-tot;
}
for (int i=1;i<=q;i++){
printf("%lld\n",Ans[i]);
}
return 0;
}
A Amino Acids
模拟题,搜索的部分很简单,就在于输出有点麻烦,但是可以把式子分成几部分输出,因为很多地方其实都是一样的,总的来说这道模拟没有非常困难,但是要注意细节
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=20;
map<string,int>mp;
int hi[maxn]={7,9,7,7,11,9,5,13,7,9};
int cnt[maxn]={89,132,133,121,146,147,75,149,105,119};
char G[maxn][maxn][maxn]={{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" O=C-N-H ",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" O=C-O-H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-S-H ",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H-C-H ",
" | ",
" O=C-N-H ",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H-C-H ",
" | ",
" O=C-O-H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H-C-H ",
" | ",
" S ",
" | ",
" H-C-H ",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-O-H ",
" | ",
" H "
},{
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-O-H ",
" | ",
" H-C-H ",
" | ",
" H "
}
};
int m,n;
vector<int>p;
vector< int >ans[100000];
int anstop;
vector<int>sta;
void dfs(int num,int sum){
if(sum>n) return;
if(num>1){
for (int i=0;i<sta.size();i++) {
ans[anstop].push_back(sta[i]);
}
anstop++;
}
for (int i=0;i<p.size();i++){
sta.push_back(p[i]);
dfs(num+1,sum+cnt[p[i]]-18);
sta.pop_back();
}
}
void print(){
for (int t=0;t<anstop;t++){
auto x=ans[t];
printf("\n");
int h=0;
for (auto id:x){
h=max(h,hi[id]);
}
//cout<<h<<endl;
for (int i=0;i<2;i++){
for (int j=0;j<x.size();j++){
for (int k=0;k<8;k++)
printf("%c",G[x[j]][i][k]);
}
printf("\n");
}//先打上面两行
int oo=(int)x.size()-1;
for (int j=0;j<=oo;j++){
int k=0;
if(j!=0) k=2;
if(j!=oo){
for (;k<8;k++)
printf("%c",G[x[j]][2][k]);
printf("--");
}else {
for (;k<11;k++)
printf("%c",G[x[j]][2][k]);
}
}//打中间一行*/
printf("\n");
for (int i=3;i<h;i++){
for (int j=0;j<x.size();j++){
int k=0;
if(j!=0) k=2;
if(i>=hi[x[j]]) {
for (;k<10;k++) printf(" ");
}else {
for (;k<10;k++)
printf("%c",G[x[j]][i][k]);
}
}
printf("\n");
}//先打上面两行
}
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
mp["Ala"]=0;
mp["Asn"]=1;
mp["Asp"]=2;
mp["Cys"]=3;
mp["Gln"]=4;
mp["Glu"]=5;
mp["Gly"]=6;
mp["Met"]=7;
mp["Ser"]=8;
mp["Thr"]=9;
cin>>m>>n;
for (int i=1;i<=m;i++){
string s;
cin>>s;
p.push_back(mp[s]);
}
sort(p.begin(),p.end());
for (int i=0;i<p.size();i++){
sta.push_back(p[i]);
dfs(1,cnt[p[i]]);
sta.pop_back();
}
//sort(ans.begin(),ans.begin()+anstop);
printf("%d",(int)anstop);
print();
return 0;
}
G-Glass Bead Game
题意:有n个小球排成一排,每个小球给出一个概率p[i],按照给出的概率随机选取一个小球,将其放到最前面,每次操作的代价是该球前面求的个数,问在操作无限次后再操作一次,操作代价的期望是多少.
做法:最终的期望就等于\(\sum_{i=1}^{n}p[i]*排在i前的个数\),所以我们还需要求排在i前的球个数的期望,这个等于\(\sum_{i!=j}\frac{p[j]}{p[j]+p[i]}\),
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
double p[maxn];
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int n;
cin>>n;
for (int i=1;i<=n;i++) scanf("%lf",&p[i]);
double ans=0;
for (int i=1;i<=n;i++){
double res=0;
for (int j=1;j<=n;j++){
if(i!=j)
res+=(p[j])/(p[i]+p[j]);//排在i之前的个数的期望
}
ans+=p[i]*(res);
}
printf("%.12lf\n",ans);
return 0;
}
B Blocks
首先考虑怎么推期望,设状压dp[i]表示i状态距离铺满的状态所需要的期望步数,那么dp[0]就是答案,
对于状态i不是铺满的 \(dp[i]=1+\frac{1}{n}\sum_{j=0}^{n}{dp[i⊕(1<<j)]}\) 对于状态i = i⊕(1<<j)的,即需要用到dp[i]自身,设i中1的个数为cnt,
这个时候简单推导一下,式子就成了
变换一下,就是$$dp[i]=\frac{n+c}{n-cnt}$$
所以只需要解决dp[i]=0的情况就可以了,就是面积铺满的情况,这个情况的话,我们可以用容斥定理,统计出每个块的交集,然后枚举子集用容斥定理通过交集求出总的并集,判断面积即可,感觉其实还是蛮难做的一道题,不知道榜上为啥那么多人过,感觉G和E其实更好想,也更好写
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=12;
typedef long long ll;
const ll mod=998244353;
struct my{
int x,y;
}a[N],b[N];
ll dp[1<<N],area[1<<N],f[1<<N];
inline int check(int x,int n){
int num=0;
for (int i=0;i<n;i++) if(((x>>i)&1)) num++;
return (num&1) ? 1 : -1;
}
ll p[N];
ll quickpow(ll a,ll b){
ll ans=1;
for (;b;b>>=1){
if(b&1ll) ans=ans*a%mod;
a=a*a%mod;
}
return ans%mod;
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int _;
cin>>_;
for (int i=0;i<N;i++) p[i]=quickpow(i,mod-2);
while(_--){
int n;
int W,H;
scanf("%d",&n);
cin>>W>>H;
for (int i=0;i<n;i++) scanf("%d%d%d%d",&a[i].x,&a[i].y,&b[i].x,&b[i].y);
for (int i=1;i<(1<<n);i++){
area[i]=0;
int xl=0,yl=0,xr=W,yr=H;
for (int j=0;j<n;j++){
if(((i>>j)&1)){
xl=max(a[j].x,xl);
xr=min(b[j].x,xr);
yl=max(a[j].y,yl);
yr=min(b[j].y,yr);
}
}
if(xr<xl) xr=xl;
if(yr<yl) yr=yl;
area[i]=(ll)(xr-xl)*(yr-yl);
}
for (int i=1;i<(1<<n);i++){
dp[i]=0;
for (int s=i;s;s=(s-1)&i){//枚举i的子集
dp[i]=dp[i]+area[s] * check(s,n);
}
//printf("%lld\n",dp[i]);
}
// cout<<dp[(1<<n)-1]<<" "<<W*H<<endl;
if(dp[(1<<n)-1]!=(ll)W*H){
printf("-1\n");
continue;
}
ll mj=(ll)W*H;
for (int i=(1<<n)-1;i>=0;i--){
f[i]=0;
ll cnt=0,num=0;
if(dp[i]==mj) continue;
for (int j=0;j<n;j++){
if(((i>>j)&1)) cnt++;
else num=(num+f[(i^(1<<j))])%mod;
}
f[i]=(n+num)*(p[n-cnt])%mod;
}
printf("%lld\n",f[0]%mod);
}
return 0;
}