蒟蒻就切了四道水题,然后EF看着可写然而并不会,中间还WA了一次,我太菜了.jpg =。=
A.Vasya And Password
一开始看着有点虚没敢立刻写,后来写完第二题发现可以暴力讨论,因为保证有解,没了
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
char rd[N];
int main ()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s",rd);
int c1=,c2=,c3=,len=strlen(rd);
for(int i=;i<len;i++)
{
if(rd[i]>=''&&rd[i]<='') c1++;
if(rd[i]>='A'&&rd[i]<='Z') c2++;
if(rd[i]>='a'&&rd[i]<='z') c3++;
}
if(!c1&&!c2&&c3)
rd[]='',rd[]='A';
else if(!c1&&c2&&!c3)
rd[]='',rd[]='a';
else if(c1&&!c2&&!c3)
rd[]='a',rd[]='A';
else if(c1&&c2&&!c3)
{
if(c1>)
{
for(int i=;i<len;i++)
if(rd[i]>=''&&rd[i]<='') {rd[i]='a';break;}
}
else if(c2>)
{
for(int i=;i<len;i++)
if(rd[i]>='A'&&rd[i]<='Z') {rd[i]='a';break;}
}
}
else if(c1&&!c2&&c3)
{
if(c1>)
{
for(int i=;i<len;i++)
if(rd[i]>=''&&rd[i]<='') {rd[i]='A';break;}
}
else if(c3>)
{
for(int i=;i<len;i++)
if(rd[i]>='a'&&rd[i]<='z') {rd[i]='A';break;}
}
}
else if(!c1&&c2&&c3)
{
if(c3>)
{
for(int i=;i<len;i++)
if(rd[i]>='a'&&rd[i]<='z') {rd[i]='';break;}
}
else if(c2>)
{
for(int i=;i<len;i++)
if(rd[i]>='A'&&rd[i]<='Z') {rd[i]='';break;}
}
}
for(int i=;i<len;i++) printf("%c",rd[i]); printf("\n");
}
return ;
}
B.Relatively Prime Pairs
想了一会一拍脑袋,这题\*\*这么水还想啥,输出相邻数字,没了
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long l,r;
int main ()
{
scanf("%lld%lld",&l,&r);
printf("YES\n");
for(long long i=l;i<=r;i+=)
printf("%lld %lld\n",i,i+);
return ;
}
C.Vasya and Multisets
需要稍微想一想的题,然而本质上还是个分类讨论
考虑每种数字出现不同次数带来的影响,出现一次的一定对一个集合有贡献,出现两次的怎么放都没有影响,出现三次及以上的可以令它对一个集合有贡献,也可以令它没有影响
所以首先除去所有出现两次的数(都丢给一个集合即可),然后看看出现一次的有奇数个还是偶数个。如果有奇数个且没有出现三次以上的则无解;如果有奇数个且有出现三次及以上的就先两两分开出现一次的,把剩下那个放在一个集合,出现三次及以上的一个数放一个在另一个集合,然后剩下的一股脑丢在一个集合里;如果有偶数个就两两分开然后剩下的直接丢在一个集合里
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
long long num[N],cnt[N];
char outp[N];
bool used[N];
long long n,und,ans,k;
int main ()
{
scanf("%lld",&n);
for(int i=;i<=n;i++)
scanf("%lld",&num[i]),cnt[num[i]]++;
for(int i=;i<=;i++)
if(cnt[i]==)
{
for(int j=;j<=n;j++)
if(num[j]==i) used[j]=,outp[j]='A';
}
else if(cnt[i]>=)
{
und++;
for(int j=;j<=n;j++)
if(num[j]==i) used[j]=;
}
// for(int i=1;i<=n;i++) printf("%c",outp[i]);
for(int i=;i<=n;i++) if(!used[i]) ans++,outp[i]=(k^=)?'A':'B';
if((ans&)&&!und) {printf("NO");return ;}
else
{
if((ans&)==)
{for(int i=;i<=n;i++) if(used[i]) outp[i]='A';
}
else
{
bool flag=true;
for(int i=;i<=n;i++) if(used[i]&&cnt[num[i]]!=)
{
if(flag)outp[i]='B',flag=false;
else outp[i]='A';
}
}
}
printf("YES\n");
for(int i=;i<=n;i++)
printf("%c",outp[i]);
return ;
}
D.Bicolorings
递推题,画图推式子即可
设$rec[i][j][0/1][0/1]$表示到第$i$列分出了$j$块,且第$i$列上面为白/黑,下面为白/黑的方案数,这样就足够递推了,讨论新加入的两块与之前的联通情况即可
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
const long long mod=;
long long rec[N][*N][][];
long long n,m;
int main ()
{
scanf("%lld%lld",&n,&m);
rec[][][][]=rec[][][][]=;
rec[][][][]=rec[][][][]=;
for(int i=;i<=n;i++)
for(int j=;j<=*i;j++)
{
rec[i][j][][]+=rec[i-][j][][]+rec[i-][j][][]+rec[i-][j][][]+rec[i-][j-][][];
rec[i][j][][]+=rec[i-][j][][]+rec[i-][j][][]+rec[i-][j][][]+rec[i-][j-][][];
rec[i][j][][]+=rec[i-][j][][]+rec[i-][j-][][]+rec[i-][j-][][]; if(j>=) rec[i][j][][]+=rec[i-][j-][][];
rec[i][j][][]+=rec[i-][j][][]+rec[i-][j-][][]+rec[i-][j-][][]; if(j>=) rec[i][j][][]+=rec[i-][j-][][];
rec[i][j][][]%=mod;rec[i][j][][]%=mod;rec[i][j][][]%=mod;rec[i][j][][]%=mod;
}
printf("%lld",(rec[n][m][][]+rec[n][m][][]+rec[n][m][][]+rec[n][m][][])%mod);
return ;
}
E.Vasya and Big Integers
看这个范围,显然是个线性DP题然而你是个不会做DP的鶸
看了看AC代码没看懂,先咕着,等官方题解=。=
哦官方题解我也没看懂,弃疗了
F.The Shortest Statement
题目并不难......
看出题人都特意提示了$m-n<=20$我居然还没做出来,我太菜了=。=
先随便跑一棵搜索树出来,对于指回祖先的边特殊记录下这些祖先,然后continue掉,单独把这些祖先拿出来预处理最短路,然后回答询问的时候除了在搜索树上求一个树上距离再考虑一下这些祖先到两点的最短路之和就行了
调试的时候持续石乐志,出了一堆**错误......
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=,K=;
struct a
{
int node;
long long dist;
};
bool operator < (a x,a y)
{
return x.dist>y.dist;
}
priority_queue<a> hp;
int p[N],noww[*N],goal[*N],mem[K];
int siz[N],dep[N],anc[N],imp[N],top[N];
long long val[*N],dis[N],diss[K][N];
int n,m,q,t1,t2,cnt,len,tot;
long long t3;
bool vis[N],bac[N];
void link(int f,int t,long long v)
{
noww[++cnt]=p[f],p[f]=cnt;
goal[cnt]=t,val[cnt]=v;
}
void DFS(int nde,int fth,int dth)
{//printf("%d->%d\n",fth,nde);
int tmp=;
siz[nde]=,anc[nde]=fth,dep[nde]=dth;
for(int i=p[nde];i;i=noww[i])
if(goal[i]!=fth)
{
if(dep[goal[i]])
{
if(dep[goal[i]]<dep[nde])
mem[++len]=goal[i];
continue;
}
dis[goal[i]]=dis[nde]+val[i];
DFS(goal[i],nde,dth+);
siz[nde]+=siz[goal[i]];
if(siz[goal[i]]>tmp)
tmp=siz[goal[i]],imp[nde]=goal[i];
}
}
void MARK(int nde,int tpp)
{
top[nde]=tpp;
if(imp[nde])
{
MARK(imp[nde],tpp);
for(int i=p[nde];i;i=noww[i])
if(goal[i]!=anc[nde]&&goal[i]!=imp[nde])
if(dep[goal[i]]>dep[nde]) MARK(goal[i],goal[i]);
}
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y); x=anc[top[x]];
}
return dep[x]<dep[y]?x:y;
}
void Dijkstra(int nde,int typ)
{
memset(diss[typ],0x3f,sizeof diss[typ]);
memset(vis,,sizeof vis); diss[typ][nde]=; hp.push((a){nde,});
while(!hp.empty())
{
a tt=hp.top(); hp.pop(); int tn=tt.node;
if(vis[tn]) continue; vis[tn]=true;
for(int i=p[tn];i;i=noww[i])
if(diss[typ][goal[i]]>diss[typ][tn]+val[i])
{
diss[typ][goal[i]]=diss[typ][tn]+val[i];
hp.push((a){goal[i],diss[typ][goal[i]]});
}
}
}
long long getdis(int n1,int n2)
{
int lca=LCA(n1,n2);
return dis[n1]+dis[n2]-*dis[lca];
}
int main ()
{
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++)
{
scanf("%d%d%lld",&t1,&t2,&t3);
link(t1,t2,t3),link(t2,t1,t3);
}
DFS(,,); MARK(,);
sort(mem+,mem++len);
len=unique(mem+,mem++len)-mem-;
for(int i=;i<=len;i++) Dijkstra(mem[i],i);
scanf("%d",&q);
while(q--)
{
scanf("%d%d",&t1,&t2);
long long ans=getdis(t1,t2);
for(int i=;i<=len;i++)
ans=min(ans,diss[i][t1]+diss[i][t2]);
printf("%lld\n",ans);
}
return ;
}
G.Distinctification
看了官方题解,大概了解是什么思路了,但是这题解真的挺抽象的=。=
我们假设这些$pair$已经按照$a$从小到大排好序,那么我们考虑分成若干段来处理这个问题,分割的标准就是这一段首尾的距离大于等于首尾之差,也就是说这一段需要通过操作$1$来变得合法,我们就将它们划成一段,来在段内处理问题。
那么段内如何处理呢?显然我们首先将它调整成合法的情况,然后应该在段内将$b$从大到小排序,这样进行操作$2$最优,那么我们就需要用一个数据结构来维护段的信息。最后当我们处理完了所有的段,我们还需要快速将它们合并。对于合并,我们检查新加进来的$pair$左右相邻的位置,看看是否有其他段的右/左端点,有就合成一段。主要思想大概就是这样,然后说一点具体实现
对于段内的统计,我们需要统计一段区间(权值)$(l,r)$的左边的总和乘上右边的数量,然后减掉一个前缀和,统计的部分可以用权值线段树来实现。然后对于段的维护可以使用并查集,最后是段的合并,因为用线段树维护,所以就用线段树合并做就(=。=?)好了
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=,M=;
long long sum[M],val[M],fsum[N];
int node[N],siz[M],son[M][];
int e[N],a[N],b[N],aset[N];
long long n,c,cnt;
int finds(int x)
{
return x==aset[x]?x:aset[x]=finds(aset[x]);
}
void pushup(int nde)
{
int ls=son[nde][],rs=son[nde][];
sum[nde]=sum[ls]+sum[rs],siz[nde]=siz[ls]+siz[rs];
val[nde]=val[ls]+val[rs]+1ll*sum[ls]*siz[rs];
}
void Create(int &nde,int l,int r,int pos,int task)
{
if(!nde) nde=++cnt;
if(l==r)
siz[nde]=,sum[nde]=val[nde]=task;
else
{
int mid=(l+r)/;
if(pos<=mid) Create(son[nde][],l,mid,pos,task);
else Create(son[nde][],mid+,r,pos,task); pushup(nde);
}
}
int Merge(int x,int y)//线段树合并
{
if(!x||!y) return x|y; int tmp=x;
son[tmp][]=Merge(son[x][],son[y][]);
son[tmp][]=Merge(son[x][],son[y][]);
if(!son[tmp][]&&!son[tmp][])
{
val[tmp]=val[x]+val[y]+1ll*sum[y]*siz[x];
sum[tmp]=sum[x]+sum[y];
siz[tmp]=siz[x]+siz[y];
}
else pushup(tmp);
return tmp;
}
long long query(int nde)
{
int nd=node[nde];
return val[nd]+1ll*(nde-)*sum[nd];
}
void gather(int a,int b)
{
int f1=finds(a),f2=finds(b);//合并两段
aset[f2]=f1,c-=query(f1),c-=query(f2);
node[f1]=Merge(node[f1],node[f2]),c+=query(f1);
}
int main ()
{
scanf("%lld",&n);
for(int i=;i<=;i++) aset[i]=i;
for(int i=;i<=n;i++)
{
scanf("%d%d",&a[i],&b[i]);
fsum[i]=fsum[i-]+1ll*a[i]*b[i];//预处理前缀和
a[i]=finds(a[i]),aset[a[i]]=a[i]+;//并查集维护段的情况
Create(node[a[i]],,,b[i],b[i]);
}
for(int i=;i<=;i++) aset[i]=i;
for(int i=;i<=n;i++)
{
c+=query(a[i]);
if(e[a[i]+]) gather(a[i],a[i]+);//检查左右是否有段
if(e[a[i]-]) gather(a[i]-,a[i]);
e[a[i]]=true,printf("%lld\n",c-fsum[i]);
}
return ;
}