2017.10.16 队内互测 D4

题目来源:
T1:codevs 2913 建筑抢修
T2:codevs 1089 侦探推理
T3:luogu 2246
T4:luogu 3927

ps:T2没有重新做,太恶心了

T1:这里写图片描述
这里写图片描述

—考试的时候脑子w了,瞪它了一个小时死活思考不下去,做完后面回来看这道题的时候,直接看不下去了。。最后随便贪心了一下结果就过了一个点。。。
其实这道题本身就是贪心。
优先选择结束时间早的,占用时间短的放在前面。
但是,如果我们先放结束时间早的,占用时间有可能长。这样肯定不是最优的
假设我们暂时按照结束时间的顺序来完成,我们来模拟一下这个过程。一开始一直放,直到有一个任务发现完不成了(也就是前面已经完成的任务占用的时间加上它自己所用的时间大于它的截稿时间),这时我们不能直接舍弃。我们看一下,在前面所有已经完成的任务里,哪一个占用的时间最长。如果这个占用时间最长的任务比现在的占用时间长,那我们把它替换掉,虽然对答案没有贡献,但是显然对于后面的更优。反之,我们直接丢掉。所以我们开一个大根堆来记录之前已经完成的任务的占用时间,从第一个到第n个for一遍,每次按照贪心的原则,最后所得的答案就是最优的答案。
这是出题人的题解:

一眼看上去它是个贪心,事实上,它就是个贪心。
但需要好好想想怎么贪。
正解贪心+堆。
先按截稿时间/花费时间从小到大来排一遍序,因为截稿时间靠前的肯定要先选,截稿时间相同的肯定要选时间短的。
然后我们建个堆,堆顶是花费时间最大的元素。
① 然后从第一个开始,若当前任务所需时间+之前的总时间小于等于当前截稿时间,则扔进堆里,ans++。//能够完成,更新tot
若大于,这时要从它前面所有任务中选一个花费时间最长的(就是堆顶元素,记为y)和它比较,
②若当前任务所需时间 > y,则不扔进堆中。//若替换,当前任务仍然不能完成;
③若当前任务所需时间 < y,则把y替换成当前任务。//当前任务能够完成,ans数不变,使得now减小,更加优;
这样能保证时间利用率最高,完成更多任务。
正确性证明:
上述;
有没有一种操作,弹出花费时间最大的任务y,使tot减小;
没有
如果后面仍然没有能够完成的,这样会使ans–;
如果后面有能够完成的,它的时间比y大,不优;
它的时间比y小,会在③操作中实现。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

priority_queue<int>q;
const int maxn=150010;
int n,ans,now;
struct hh
{
    int len,end;
}e[maxn];

bool cmp(hh x,hh y)
{
    return x.end<y.end;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
      scanf("%d%d",&e[i].len,&e[i].end);
    sort(e+1,e+n+1,cmp);
    ans=1;
    q.push(e[1].len);
    now=e[1].len;
    for(int i=2;i<=n;++i)
    {
        if(now+e[i].len<=e[i].end)
        {
            ans++;
            q.push(e[i].len);
            now+=e[i].len;
        }
        else
        {
            int l=q.top();
            if(l>e[i].len)
            {
                q.pop();
                q.push(e[i].len); 
                now=now-l+e[i].len;
            }
        }
    }
    printf("%d",ans);
    return 0;
}

T2:(粘个题面)
这里写图片描述
这里写图片描述
这里写图片描述
这是一道恶心的搜索(预处理恶心)

T3:
这里写图片描述
这里写图片描述

当时还有半个小时的时候打了个暴力,20分;
后来才知道原来是一个DP,而且代码贼简单
定义dp[i][j]为:原文匹配到第i个字符,“helloworld”匹配到第j个字符时的方案数。
转移:
①:当前第i位与第j位不匹配:dp[i][j]=dp[i-1][j]
②:匹配:dp[i][j]=dp[i-1][j]+dp[i-1][j-1]
初始化:dp[0][0]=1;
解释一下转移方程:
第①种情况:不必多说
第②种情况:dp[i-1][j]表示匹配到原文的上一个字符时,到“helloworld”的第j个字符有多少种方案。dp[i-1][j-1]表示匹配到原文上一个字符时,到“helloworld”的第j-1个字符有多少种方案。也就是说,当前的方案数是:当前的字符替换掉上一个字符产生的方案数和它本身与前面产生的方案数。
对于初始化:为了方便第一个匹配的字符的转移,直接赋值为1

有一些小问题:
①:while((c=getchar())!=EOF),这时会读入空格和回车,结果一交就RE了,(数组放不下)我就改成了cin
②:如果我把一个数组这样赋值:如:dp[30]={1},这时只把数组的第一个赋值为1,也就是只有dp[0]=1,其他的还是0

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int mo=1e9+7;
int dp[500010][15];
char it[12]="!helloworld";

bool judge(char x,char y)
{
    if(x==y) return true;
    if(x==y+32) return true;
    if(y==x+32) return true;
    return false;
}
int main()
{
    char c;
    int cnt=0;
    while(cin>>c)
    {
        dp[cnt++][0]=1;
        for(int i=1;i<=10;++i)
        {
            dp[cnt][i]=dp[cnt-1][i];
            if(judge(c,it[i]))
              dp[cnt][i]=(dp[cnt][i]%mo+dp[cnt-1][i-1]%mo)%mo;
        }
    }
    printf("%d",dp[cnt][10]);
    return 0;
}

可不可以优化空间?
受背包思想的影响,当然可以把第一维省掉啊!不用滚动数组,只要倒序处理就可以了,这样保证每次转移都是从上一层转移过来的,时间复杂度不变。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int mo=1e9+7;
char it[15]="!helloworld";
int dp[11]={1},cnt=0;
char c;

bool judge(char x,char y)
{
    if(x==y) return true;
    if(x==y+32) return true;
    if(y==x+32) return true;
    return false;
}

int main()
{
    while((c=getchar())!=EOF)
    {
        for(int i=10;i>=1;--i)
          if(judge(c,it[i]))
            dp[i]=(dp[i-1]+dp[i])%mo;
    }
    printf("%d",dp[10]);
}

PS:个人习惯,不区分大小写时,判断两个字符是否相等,喜欢写一个bool函数

T4:
这里写图片描述
这里写图片描述

这是什么题?数论?不知道。分解质因数呗。
考试的时候,不会快速分解k的质因数,就只做了70%,结果还莫名其妙wa了1个点,得了65.
之前做过一道很像的题。不过那道题没有k进制的限制,是在十进制下的。
当时的做法是:在!n中寻找2和5出现的次数,取min。原因:将10分解质因数可得2*5=10;找到相同数量的2和5,就有多少个10,也就有多少个0;
现在变成k进制了,所以我们要把k分解质因数。
末尾零的个数,还要作一些变化。
比如将20分解质因数:20=2^2*5。这时,我们要找的2的个数还要除以2才对。
问题转化为:如何快速求k的质因数
答案是,从2for到根号k就行了。
先看一下如何处理:
先从2开始,如果k%2==0,说明2是n的质因数。然后我把k一直除以2,直到k%2!=0,这样,统计了质因数2的个数,同时立刻统计一下!k里2的个数,更新ans(记得取min)
这样for到最后,我们看一下k还剩下什么。
由于我们是for到根号k,可以知道,k的质因数里面,最多有一个是大于根号k的,(如果有两个,乘起来大于k,不成立)。
k要么变成了1,要么变成了最大的那个质因数。
如果k不是1,我们再把这个k(也就是原先k的质因数)统计一下,更新ans就行了
这样,在分解k的质因数的同时,统计了!n里面所有的k的质因数的个数。
最后直接输出答案。

有两次交上都是崩溃&&WA&&TLE。
第一遍发现有两个循环里忘了把循环变量改成long long,然后死循环了,并且整数被零除。
第二次又T了好几组。死活没看出来。
后来调了半天才发现第一遍改的时候漏掉了一个for循环。。。
教训:以后只要用long long,就把除了int main里面的int,全部改为long long吧
对了,别忘了把ans初始值赋为极大值
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const long long inf=0x7fffffffffffffff;
long long n,k,ans;

int main()
{
    scanf("%lld%lld",&n,&k);
    ans=inf;
    for(long long i=2;i*i<=k;++i)//一定要全部改成long long 
    {
        long long tot=0;
        while(k%i==0) 
        {
            k/=i;
            tot++;
        }
        if(tot==0) continue;
        long long rest=0;
        for(long long j=i;j<=n;j*=i) rest+=n/j;
        ans=min(ans,rest/tot);
    }
    if(k!=1)
    {
        long long rest=0;
        for(long long j=k;j<=n;j*=k) 
          rest+=n/j;
        ans=min(ans,rest);
    }
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值