CF 463A && 463B 贪心 && 463C 霍夫曼树 && 463D 树形dp && 463E 线段树
http://codeforces.com/contest/462
要求是否全部的字符都挨着偶数个'o'
#include <cstdio> using namespace std; char maz[101][101]; int n; int cnt[101][101]; const int dx[4]={0,0,-1,1}; const int dy[4]={1,-1,0,0}; int main(){ scanf("%d",&n); gets(maz[0]); for(int i=0;i<n;i++){ gets(maz[i]); } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(maz[i][j]=='o'){ for(int k=0;k<4;k++){ int nx=i+dx[k]; int ny=j+dy[k]; if(nx<n&&ny<n&&nx>=0&&ny>=0){cnt[nx][ny]++;} } } } } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(cnt[i][j]&1){puts("NO");return 0;} } } puts("YES"); return 0; }
贪心
#include <cstdio> #include <algorithm> using namespace std; long long n,k; long long cnt[26]; char maz[200001]; int main(){ scanf("%I64d%I64d",&n,&k); gets(maz); gets(maz); for(int i=0;i<n;i++)cnt[maz[i]-'A']++; sort(cnt,cnt+26); long long ans=0; for(int i=25;i>=0;i--){ if(cnt[i]>=k){ ans+=k*k; k=0; } else { k-=cnt[i]; ans+=cnt[i]*cnt[i]; } if(k==0)break; } printf("%I64d\n",ans); return 0; }
题解意思:
这是一道霍夫曼编码的反问题,把所有边权取反就是霍夫曼树问题,详细证明请看算法导论,大概证明一下:
可以把每次的拿来拆分的每一段的都作为树上的节点,长度为1的值必然是叶节点,会在统计一次后被抛出,这时父节点权值则是子节点权值之和,而且必然是一颗二叉树,这与题目是符合的,也就是说深度越大的叶节点加和的次数越多,那么把最大的那两个节点先放在树里,那么它们的父节点就拥有两个节点之和的性质,那么再添加次大的,如此构建就形成了整棵最优树,(算法导论可以严格证明这是棵最优树,通过反证法),于是除了最大的两个点加了n次,其余的点分别加了n-1,n-2,....2次
#include <cstdio> #include <algorithm> using namespace std; const int maxn=300005; int n; long long a[maxn]; int main(){ scanf("%d",&n); long long ans=0,sum=0; for(int i=0;i<n;i++){scanf("%I64d",a+i);sum+=a[i];} sort(a,a+n); ans=sum; for(int i=1;i<n;i++){ ans+=sum; sum-=a[i-1]; } printf("%I64d\n",ans); return 0; }
D:Appleman and Tree
树形dp
这是一道树形dp,我一开始想到奇怪的地方去了,什么连通域之类的
对每个点,dp记录使其的所有子树成为单黑或纯白的方式,最后再加上自身的黑/白属性
具体来说
初始化:子树单黑=0,子树纯白=1
单黑=现有单黑*子树纯白+子树单黑*现有纯白
纯白=现有纯白*子树纯白
然后加入自身的属性
如果自身为黑,那么单黑=子树纯白(现有黑:切掉所有子节点成为0)
如果自身为白,那么纯白*=子树纯白(切掉本身使得孤立的方式更多)
#include<cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int maxn=100005; const long long mod=1000000007 ; long long dp[maxn][2]; int color[maxn]; vector<int> G[maxn]; bool vis[maxn]; void dfs(int s){ dp[s][0]=1; dp[s][1]=0; vis[s]=true; for(int i=0;i<G[s].size();i++){ int t=G[s][i]; if(vis[t])continue; dfs(t); dp[s][1]=(dp[s][0]*dp[t][1]+dp[s][1]*dp[t][0])%mod; dp[s][0]=dp[s][0]*dp[t][0]%mod; } if(color[s]==1){ dp[s][1]=dp[s][0]; } else dp[s][0]+=dp[s][1]; dp[s][1]%=mod; dp[s][0]%=mod; } int main(){ int n; scanf("%d",&n); for(int i=1;i<n;i++){ int temp; scanf("%d",&temp); G[temp].push_back(i); } for(int i=0;i<n;i++)scanf("%d",color+i); dfs(0); printf("%I64d\n",dp[0][1]); return 0; }
E:Appleman and a Sheet of Paper
线段树
翻折,当长度>len/2的时候那么就反向翻折,这时候相当于反向了一次,需要反着来统计
看上去思路很清晰但是很麻烦
#include<cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int maxn=100005; const int maxnode=400005; long long w[maxnode]; int cur,s,e,len,n,q; int num(int i){//返回正确的标号,传入的是从开始的端点所需经过距离 if(cur==0){return s+i;} return e-i; } void update(int k,int d){//更新线段树 int tk=k; k+=n-1; w[k]+=d; while(k>0){ k=(k-1)/2; w[k]+=d; } } void inv(int l){//翻转 int tl; if(l*2>len){tl=len-l;cur^=1;}//翻转统计的同时就要反向更新了 else tl=l; for(int i=0;i<tl;i++){ int td=num(tl*2-1-i); int td2=num(i); update(td,w[td2+n-1]); update(td2,-w[td2+n-1]); } if(cur)e-=tl;else s+=tl; len=e-s+1; } long long query(int a,int b,int k,int l,int r){ if(r<=a||l>=b||l>=r)return 0; if(a<=l&&r<=b){ return w[k]; } else { long long v1=query(a,b,k*2+1,l,(l+r)/2); long long v2=query(a,b,k*2+2,(l+r)/2,r); return v1+v2; } } int main(){ scanf("%d%d",&n,&q);int tn=n,ttn=n;n=1; while(tn>0){n<<=1;tn>>=1;} for(int i=0;i<ttn;i++)update(i,1); s=0;e=ttn-1;len=ttn; while(q--){ int op; scanf("%d",&op); if(op==1){ int l; scanf("%d",&l); inv(l); } else{ int l,r; scanf("%d%d",&l,&r); if(cur){ l=num(l-1);//这里卡我半天因为题目给的是从s开始的序号所以从e开始就要+1,传进的参数要-1 r=num(r-1); } else { l=num(l); r=num(r); } if(l>r)swap(l,r); long long ans=query(l,r,0,0,n); printf("%I64d\n",ans); } } }