p1552[APIO2012] 派遣 紫
题意:master可以向所有被派遣的忍者发送指令,master自己可以被派遣,也可以不派遣,如果master没有派遣,就不需要支付master的薪水。上级给直属下级发送消息。每个忍者的上级bi,薪水ci,领导力li,满意度为派遣人数×master的领导力水平,输出满意度最大值
分析:用siz记录派遣人数,薪水总数sum,用树形dp,如果薪水总数超过预期数m,可以把让根节点不去,用左偏树删除。
代码:
#include<bits/stdc++.h> #define maxn 200010 using namespace std; typedef long long ll; ll n,m,root,ans; ll fa[maxn],ls[maxn],rs[maxn],dis[maxn],val[maxn],l[maxn],sum[maxn],siz[maxn]; struct edge { int to,nxt; }e[maxn]; int head[maxn],edge_cnt; void add(int from,int to) { e[++edge_cnt]=(edge){to,head[from]}; head[from]=edge_cnt; } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } int merge(int x,int y) { if(x==y) return 0; if(!x||!y) return x+y; if(val[x]<val[y]) swap(x,y); rs[x]=merge(rs[x],y),fa[rs[x]]=x; if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]); if(rs[x]) dis[x]=dis[rs[x]]+1; else dis[x]=0; return x; } void del(int x) { fa[ls[x]]=ls[x],fa[rs[x]]=rs[x]; fa[x]=merge(ls[x],rs[x]); } void dfs(int x) { for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to;//儿子 dfs(y); siz[x]+=siz[y];//当前长度加上儿子的长度 sum[x]+=sum[y];//加上薪水 merge(find(x),find(y));//合并子树 } while(sum[x]>m)//如果薪水总和大于m { int rootx=find(x);//根 siz[x]--;//减少子树 sum[x]-=val[rootx];//减少子树总和 del(rootx);//删除根 } ans=max(ans,l[x]*siz[x]); } int main() { cin>>n>>m; for(int i=1;i<=n;++i){ int fath; cin>>fath>>val[i]>>l[i]; sum[i]=val[i],siz[i]=1,fa[i]=i; if(fath) add(fath,i);//建立边 else root=i; } dfs(root); cout<<ans<<endl; return 0; }
P2462 [SDOI2007] 游戏 紫
题意:给定n个字符串,输出规则:前一个单词拥有所有的字符在后一个单词里必须出现,而字母出现次数不少于前一个单词,后一个单词的长度比前一个单词的长度恰好多1
分析:一个单词a符合两个条件,可以成为另一个单词b的下一个: 1.length(a) = length(b) + 1. 2.a中各字母出现次数≥b中各字母出现次数。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxl = 110, maxn = 10010; int head[maxl], ver[maxn], nex[maxn], tot; void add(int len, int id) { ver[tot] = id; nex[tot] = head[len]; head[len] = tot++; } char ch[maxn][maxl]; int split[maxn][26]; int len[maxn], ans[maxn], pre[maxn]; int mres = 0, maid = -1; inline bool check(int x, int y) { for(int i = 0; i < 26; i++) if(split[x][i] < split[y][i]) return false; return true; } void dp(int cur) { ans[cur] = 1;//当前字符串最长序列长度为1 for(int i = head[len[cur]-1]; i != -1; i = nex[i])//遍历长度减1的 if(check(cur, ver[i])) {//如果符合 if(!ans[ver[i]]) dp(ver[i]);//如果儿子没被搜过 if(ans[ver[i]] + 1 > ans[cur]) ans[cur] = ans[ver[i]] + 1, pre[cur] = ver[i]; } if(ans[cur] > mres) mres = ans[cur], maid = cur; } int stack[10010], st = 0; int main() { int pc = -1; memset(head, -1, sizeof(head)); memset(pre, -1, sizeof(pre)); while(scanf("%s", ch[++pc]) == 1) { len[pc] = strlen(ch[pc]); for(int i = 0; i < len[pc]; i++) split[pc][ch[pc][i]-'a']++; add(len[pc], pc);//建树 长度-序列 } for(int i = 0; i <= pc; i++) { if(ans[i]) continue;//如果计算过就跳过 dp(i); } //输出答案 printf("%d\n", mres); for(int i = maid; i != -1; i = pre[i]) stack[st++] = i; for(int i = st - 1; i > -1; --i) printf("%s\n", ch[stack[i]]); return 0; }
P2518 [HAOI2010] 计数 蓝
题意:现在给定一个数,你可以删掉这个数中的任意多个数位 0(或不删)并将其他的数位任意重新排序。请求出能产生出多少个不同的这个数小的数(注意这个数不会有前导 0)
代码:
#include<bits/stdc++.h> #define Ll long long using namespace std; Ll CC[1001][1001]; Ll C(Ll n,Ll m){ if(CC[n][m])return CC[n][m]; if(m==1)return n; if(m==0||m==n)return 1; if(m>n)return 0; CC[n][m]=C(n-1,m)+C(n-1,m-1); return CC[n][m]; } int a[10],v[100]; Ll ans; int n; char c; Ll cfb(){ Ll ans=1; int m=n; for(int i=0;i<=9;i++)if(a[i])ans*=C(m,a[i]),m-=a[i]; return ans; } int main() { while(cin>>c)if(isdigit(c))v[++n]=c-48,a[v[n]]++; int nn=n; for(int i=1;i<=nn;i++){ n--; for(int j=0;j<v[i];j++) if(a[j]){ a[j]--;ans+=cfb();a[j]++;} a[v[i]]--; } printf("%lld",ans); }
P2657 [SCOI2009] windy数 蓝
题意:不含前导零且相邻两个数字之差至少为 2的正整数被称为 windy 数。windy 想知道,在 a和b之间包括ab总共有多少个 windy 数?
代码:
#include<bits/stdc++.h> using namespace std; //设dp[i][j]为长度为i中最高位是j的windy数的个数 //方程 dp[i][j]=sum(dp[i-1][k]) 其中 abs(j-k)>=2 int p,q,dp[15][15],a[15]; void init() { for(int i=0;i<=9;i++) dp[1][i]=1; //0,1,2,3,4...9都属于windy数 for(int i=2;i<=10;i++)//长度 { for(int j=0;j<=9;j++) { for(int k=0;k<=9;k++) { if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k]; } } }//从第二位开始 每次枚举最高位j 并找到k 使得j-k>=2 } int work(int x) //计算<=x的windy数 { memset(a,0,sizeof(a)); int len=0,ans=0; while(x) { a[++len]=x%10; x/=10; } //分为几个板块 先求len-1位的windy数 必定包含在区间里的 for(int i=1;i<=len-1;i++) { for(int j=1;j<=9;j++) { ans+=dp[i][j]; } } //然后是len位 但最高位<a[len]的windy数 也包含在区间里 for(int i=1;i<a[len];i++) { ans+=dp[len][i]; } //接着是len位 最高位与原数相同的 最难搞的一部分 //难懂部分!!!! for(int i=len-1;i>=1;i--)//i从最高位后开始枚举 { for(int j=0;j<=a[i]-1;j++)//j是i位上的数 { if(abs(j-a[i+1])>=2) ans+=dp[i][j]; //判断和上一位(i+1)相差2以上 //如果是 ans就累加 } if(abs(a[i+1]-a[i])<2)break; } return ans; } int main() { init(); cin>>p>>q; cout<<work(q+1)-work(p)<<endl; return 0; }
P2106 Sam数 蓝
题意:求n位数里有多少个windy数,windy数:相邻两个数字之差不超过2
代码:
#include <bits/stdc++.h> const long long mod = 1e9 + 7; struct matrix { long long v[10][10]; // 定义矩阵结构体 }; matrix operator * (const matrix a,const matrix b) { // 重载运算符 matrix c; memset(c.v,0,sizeof(c.v)); // 别忘了清空 for(int k = 0;k < 10;k++) for(int i = 0;i < 10;i++) for(int j = 0;j < 10;j++) c.v[i][j] = (c.v[i][j] + a.v[i][k] * b.v[k][j] % mod ) % mod; // 矩阵乘法运算法则 return c; } matrix qpow(matrix a,long long v) { // 矩阵快速幂,指数要开long long matrix r; memset(r.v,0,sizeof(r.v)); // 清空! for(int i = 0;i < 10;i++) r.v[i][i] = 1; // 设置单位矩阵 while(v) { // 跟普通快速幂差不多 if(v & 1) r = r * a; a = a * a; v >>= 1; } return r; } long long n,ans; matrix a,d; // a是初始矩阵,d是递推矩阵 int main() { std::cin >> n; if(n == 1) { //特判 printf("10\n"); return 0; } memset(a.v,0,sizeof(a.v)); memset(d.v,0,sizeof(d.v)); for(int i = 1;i < 10;i++) // 设置初始矩阵 a.v[0][i] = 1; for(int i = 0;i < 10;i++) // 设置递推矩阵 for(int j = i - 2;j <= i + 2;j++) { if(j < 0) continue; if(j > 9) break; d.v[j][i] = 1; // 自动打表技术~ } a = a * qpow(d,n - 1); // 计算 for(int i = 0;i < 10;i++) ans += a.v[0][i],ans %= mod; // 累加答案 std::cout << ans << "\n"; // 输出 }