dp练习题

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"; // 输出
}
​
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值