水题,注意两个字符串可能长度不相等。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100010; map<char ,int >m; int main(){ m['a']=m['e']=m['i']=m['o']=m['u']=1; string s1,s2; while(cin>>s1>>s2) { int flag=1; if(s1.length()!=s2.length())flag=0; for(int i=0;i<s1.length();i++) { if(m[s1[i]]!=m[s2[i]]){ flag=0; break; } } if(flag)puts("Yes"); else puts("No"); } }
B. Average Superhero Gang Power
题意:有n个人,每个人都有权值,然后你有m次操作,每次操作可以删掉一个人或者给一个人的权值加一,使得最后剩下的人平均数最大。
坑爹题,后台数据太水了,一开始以为只要删的剩下权值最大的那个一人,然后全加加给这个人就好了,居然过了。
一个半小时后被hack(感谢大哥比赛中hack的我,否则要掉分了),发现比如一组样例如果全是“7 7 7 7 7”这样的就会错,因为删人不一定是最好的选择,所以就枚举删几个人,然后取最大值就好了。
比赛刚结束我的rank1200+,rejudge后变600+,新年惨案哈哈哈。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100010; double a[maxn],b[maxn]; int n,m,k; int main(){ while(cin>>n>>k>>m) { for(int i=1;i<=n;i++) { scanf("%lf",&a[i]); } sort(a+1,a+1+n); for(int i=1;i<=n;i++) { b[i]=b[i-1]+a[i]; } double ans=0; double tot,po; for(int i=0;i<=m&&i<=n-1;i++) { tot=b[n]-b[i]; po=n-i; if(i<m) tot+=min(po*k,(double)m-i); ans=max(ans,tot/po); } printf("%.8f ",ans); } }
题意:有2^n个格子,格子可能是空的,也可能站着许多人,有两种操作。操作一是把某一列格子平均分成两部分,操作二是把某一列格子摧毁,如果这一列格子上没有人,那么就需要耗费A的代价,如果格子上有na个人,那么就要耗费B*na*l 的代价,问把所有格子全部摧毁的最小代价是啥。
思路:一开始完全没思路,想了很久,有人说暴力递归可以过,然后就。。试了一下?真的过了,AC后想了一下合法性。
有一个很显然的性质,就是如果一列格子全是空的,那么必定直接付出A的代价进行摧毁,如果有人的话,就要进行考虑。但是发现最多只有k个人,也就是需要考虑的次数不会太多,所以递归并不是nlogn(n=2^30),而是klogk?
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100010; int n,k; ll A,B; ll a[maxn]; ll dfs(ll l,ll r){ ll val=upper_bound(a,a+k,r)-lower_bound(a,a+k,l); if(val==0) return A; if(l==r) return val*B; ll mid=(l+r)>>1; ll left=dfs(l,mid),right=dfs(mid+1,r); return min(left+right,(val)*B*(r-l+1)); } int main(){ while(cin>>n>>k>>A>>B) { for(int i=0;i<k;i++) { scanf("%lld",&a[i]); } sort(a,a+k); ll ans=dfs(1,(ll)pow(2,n)); printf("%lld ",ans); } }
补。
题意:
给出一列大小写都有的字符串,字符串长度为偶数,每种字符代表一种人,给出q次查询,每次查询,都是讲所有人分成两部分序列,第x个字符代表的人和第y个字符代表的人要在同一个序列里面,问这样的方案数有多少。序列内排列不同则方案不同,两个序列顺序不同则方案不同。
思路:
设将字符串合法的分成两部分的方案数是 f 。每个字符的数量是ci,n为字符串长度,m=n/2。
则答案应该是$frac{(m)! * (m)! *f *2}{ ci! *cj !*…… }$
我们发现这个式子里只有f是不知道的,f等于把x和y放入一个大小为m的背包的方案数。
如果直接证明做,时间复杂度会超时,所以我们先计算出,所有的n/2大小背包的方案数,如果我们此时把x和y的物品全部从这个背包中减去,就得到了不含x和y的背包的方案数,这个方案数和含xy是一样的。每次减完后再加回去。
而对于x和y相等的情况下,n/2的背包要么含x,要么不含x,所以此时背包数就等于f*2.
注意背包如果为空,则不要处理,否则会出错,加减的顺序也要注意。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100010; const ll p=1e9+7; int n,m; char s[maxn]; ll bur[60]; ll fac[maxn],inv[maxn],ans[100][100],dp[maxn],tep[maxn]; int find(char c){ if(c>='A'&&c<='Z')return c-'A'+26; return c-'a'; } void init(){ clr(bur,0),clr(dp,0),clr(inv,0); } ll qpow(ll a,ll b){ a%=p; ll res=1; while(b){ if(b&1)res*=a; res%=p; a*=a,a%=p; b>>=1; } return res; } int main(){ while(scanf("%s",s+1)!=EOF) { init(); n=strlen(s+1); for(int i=1;i<=n;i++) { bur[find(s[i])]++; } fac[0]=1; for(int i=1;i<=n;i++) { fac[i]=i*fac[i-1]%p; } ll num=fac[n/2]*fac[n/2]%p; for(int i=0;i<52;i++) { if(bur[i]==0)continue; inv[i]=qpow(fac[bur[i]],p-2); num=num*inv[i]%p; } // printf("num:%lld ",num); dp[0]=1; for(int i=0;i<52;i++) { if(!bur[i])continue; for(int j=n/2;j>=bur[i];j--) { dp[j]+=dp[j-bur[i]]; dp[j]%=p; } } for(int i=0;i<52;i++) { ans[i][i]=dp[n/2]; } for(int i=0;i<52;i++) { if(!bur[i])continue; for(int j=0;j<=n/2;j++)tep[j]=dp[j]; for(int j=bur[i];j<=n/2;j++) { tep[j]=(tep[j]-tep[j-bur[i]]+p)%p; } for(int k=i+1;k<52;k++) { if(!bur[k])continue; for(int j=bur[k];j<=n/2;j++) { tep[j]=(tep[j]-tep[j-bur[k]]+p)%p; } ans[i][k]=ans[k][i]=tep[n/2]*2%p; for(int j=n/2;j>=bur[k];j--) { tep[j]=(tep[j]+tep[j-bur[k]])%p; } } } cin>>m; int x,y; while(m--) { scanf("%d%d",&x,&y); x=find(s[x]),y=find(s[y]); printf("%lld ",num*ans[x][y]%p); } } }
补题。
题意:
给出一棵树,q次询问,每次询问给出k个树上的点,然后给出r,以r这个节点为根,将这k个点分成m组,要求组内的点,任意两点不能有祖先关系。求方案分配数。
思路:
参考了官方题解和Dup4的博客,加上了自己的一些理解。
当我们固定根节点为1的时候,我们设h[ i ]表示已经有几个 i 的祖先节点放入这些集合了。
$dp[ i ] [ j ]$ 表示将前 i 个点放入 j 个集合的方案数。
得到转移方程$dp[ i ] [ j ] =dp[ i -1 ][ j ]*(j - h[ i ])+dp[ i-1 ][ j-1 ]$。
这个方程的后半部分很好理解,就是把前i-1个点放完后,新开一个背包,把点放进去。
而前半部分,就是要把这个点放入已经存在的背包里面了,那(j-h[ i ] )是什么意思呢,因为我们能把当前节点放入所有没有他祖先节点的集合里,而他的祖先节点必定也不是在同一个组里的,所以方案数就是这个。并且只要我们将h[ i ]从小到大排序,然后按序插入,这样就可以保证所有祖先节点都比子节点先进行dp。
那怎么处理这个h[ i ] 呢,还是对于固定1为根节点的来说,如果我们先处理好dfs序,然后把每个节点的子树节点用BIT全部加1,那么对于i这个点的值,就是i的祖先节点的值加上本身了。
现在考虑怎么把固定根为1的树推广向普通树,关键还是这个h[ i ],那给出r,我们可以先求出r这个点的祖先节点数量,在求出每个x节点和r的lca,记做y,求出y的祖先节点个数,然后图上画一画就知道怎么转换了。
求lca这里用了树链剖分。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100010; const ll p=1e9+7; int n,q,dep[maxn],son[maxn],siz[maxn],lp[maxn],rp[maxn],top[maxn],fa[maxn]; int cnt; vector<int >ve[maxn]; int k,m,r,vec[maxn],a[maxn]; ll dp[maxn],h[maxn]; bool vis[maxn]; void init(){ for(int i=1;i<=n;i++)ve[i].clear(); cnt=0; clr(a,0); } void dfs(int u,int f){ siz[u]=1; son[u]=0; fa[u]=f; for(int i=0;i<ve[u].size();i++) { int v=ve[u][i]; if(v==f)continue; dep[v]=dep[u]+1; dfs(v,u); siz[u]+=siz[v]; if(son[u]==0||siz[v]>siz[son[u]]){ son[u]=v; } } } void gettop(int u,int to){ lp[u]=++cnt; top[u]=to; if(son[u]==0){ rp[u]=cnt; return; } gettop(son[u],to); for(auto v:ve[u]){ if(v != fa[u] &&v!=son[u]){ gettop(v,v); } } rp[u]=cnt; } int lca(int u,int v){ while(top[u]!=top[v]){ if(dep[top[u]] < dep[top[v]])swap(u,v); u=fa[top[u]]; } if(dep[u]>dep[v])swap(u,v); return u; } int lowbit(int x){ return x & (-x); } void add(int x,int val){ while(x<=n){ a[x]+=val; x+=lowbit(x); } } void update(int l,int r,int val){ add(l,val),add(r+1,-val); } int getsum(int x){ int res=0; while(x>0){ res+=a[x]; x-=lowbit(x); } return res; } int main(){ while(cin>>n>>q) { init(); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); ve[u].push_back(v); ve[v].push_back(u); } dfs(1,0); gettop(1,1); while(q--) { scanf("%d%d%d",&k,&m,&r); for(int i=1;i<=m;i++)dp[i]=0; dp[0]=1; for(int i=1;i<=k;i++) { scanf("%d",&vec[i]); vis[vec[i]]=1; update(lp[vec[i]],rp[vec[i]],1); } int ancnum=getsum(lp[r]); for(int i=1;i<=k;i++) { int x=vec[i]; int y=lca(x,r); h[x]=getsum(lp[x])+ancnum-2*getsum(lp[y])+(vis[y])-1; } for(int i=1;i<=k;i++) { vis[vec[i]]=0; update(lp[vec[i]],rp[vec[i]],-1); } sort(vec+1,vec+1+k,[](int x,int y){ return h[x]<h[y]; }); if(h[vec[k]]>=m)puts("0"); else{ for(int i=1;i<=k;i++) { int x=vec[i]; for(int j=min(i,m);j>=0;j--) { if(j<=h[x])dp[j]=0; else{ dp[j]=(dp[j]*(j-h[x]))%p+dp[j-1]; } } } ll ans=0; for(int i=1;i<=m;i++)ans=(ans+dp[i])%p; printf("%lld ",ans); } } } }