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;
}
發佈了77 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章