2017.10.18 (DP+tarjan、SPFA+字符串+腦洞、數學)

*這可是我們組自己從網上找題出的一套考試題,雖然有些水,但是個人認爲還是很不錯啦(〃’▽’〃)!!尤其是我的題面(≖ᴗ≖๑)

這裏寫圖片描述


這裏寫圖片描述

T1(沒有原題_(:з」∠)_)

注:保證臺階高度<=4

 t1是loi_dfkdsmbd自己把codevs上的爬樓梯改的原創題。(原題聽說用搜索就可以水過),不過有些學長反應沒看懂qwq,其實題面講的還是蠻清楚的。
  兩個問題的正解都是dp,但第二問可以用貪心做。
  方法一:(loi_dfkdsmbd的) 把每個不規則高度的臺階進行拆分,拆分成多個標準臺階。dp記錄高度的答案,另開一個ans數組,記錄實際臺階的答案(就是虛擬臺階的最頂層),每個不規則臺階真正用於轉移的是臺階的最頂端。每次轉移到一個實際臺階時,枚舉它可以向上的最大高度並更新答案。
  默默地貼上kd(std t1)的代碼(%%%%%)
 

#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 19260817
using namespace std;
const int MAXN = 400000 + 50;
int n,m,tot,lin,x;
long long dp[MAXN][3],ans[MAXN][3],yao[MAXN];//dp數組在這裏起到臨時存儲的作用 ,ans數組記錄真實的轉移值,yao數組是記錄臺階拆分後最頂端高度 
int main()
{
    freopen("stairs.in","r",stdin);
    freopen("stairs.out","w",stdout); 
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        yao[i]=1;
    for(int i=1;i<=m;i++)
        scanf("%lld",&x),scanf("%lld",&yao[x]);
    for(int i=1;i<=n;i++)//預處理 
    {
        lin=yao[i];
        while(lin--)
            dp[++tot][2]=1000000007;
        yao[i]=tot;
    }
    ans[0][1]=dp[yao[0]][1]=1,dp[0][2]=0;//初始化 
    for(int i=0;i<=n;i++)
    {
        ans[i][1]=dp[yao[i]][1],ans[i][2]=dp[yao[i]][2];
        for(int j=yao[i]+1;j<=yao[i]+4;j++)
            dp[j][1]=(dp[j][1]%mod+ans[i][1]%mod)%mod,dp[j][2]=min(dp[j][2],(ans[i][2]+1)%mod);//方程 
    }
    printf("%lld %lld",ans[n][1],ans[n][2]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

 方法二:(maple 的) 不拆分臺階,每次轉移到一個臺階,枚舉從它往下的臺階同時記錄高度差,如果大於4就break掉,不再進行轉移。
 這裏的dp[i][1]記錄的是第一問的答案,dp[i][2]是第二問的答案。
 代碼(。☌ᴗ☌。)
 

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

int n,m,p=19260817,a;
int h[100010],dp[100010][3];

int main()
{
    freopen("stairs.in","r",stdin);
    freopen("stairs.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) h[i]=1,dp[i][2]=1e9+7;
    for(int i=1;i<=m;++i)
    {
        scanf("%d",&a);
        scanf("%d",&h[a]);
    }
    dp[0][1]=1,dp[0][2]=0;
    for(int i=1;i<=n;++i)
    {
        int sum=h[i];
        for(int j=i-1;j>=0;--j)
        {
            if(sum>4) break;
            dp[i][1]=(dp[i][1]+dp[j][1])%p;
            dp[i][2]=min(dp[i][2],dp[j][2]+1);
            sum+=h[j];
        }
    }
    cout<<dp[n][1]<<" "<<dp[n][2];
    return 0;
}

另外這個題用搜索之前可以水70分呢,不過加強了數據後我就不知道了2333。


這裏寫圖片描述

T2(原題:洛谷 2656 採蘑菇)

這個題是一個很裸的板子題qwq。
因爲路有恢復係數、M又很大,我們發現這個這個圖是有環的。
有環代表什麼,需要tarjan縮點!!
另外環上的路都可以無數次經過,也就是說我們可以一直採一個路上的蘑菇,直到這條路上不能再長出蘑菇爲止。而這個環縮後的點權就是這個環中每條路上所能採到的最大蘑菇數之和。
縮點後的圖變成了DAG,這就好辦了,直接跑一個單源最長路記錄一下答案就行了。
正解:tarjan縮點+SPFA

因爲我從來沒打過縮點,所以代碼裏並沒有縮點重新建圖,而是直接把環當點跑的,思想都一樣啦_(:з」∠)_ (tarjan默認一個點也是環)。

代碼( • ̀ω•́ )✧

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

int N,M,top,cnt,S,cnt_scc,ans=-1;
int Dfn[80010],Low[80010],first[80010],next[200010];
int belong_scc[80010],Instack[80010],Stack[80010],de_scc[80010];
int de[80010],In[80010];
vector<int> scc[80010];
struct maple{
    int f,t,d;
    double x;
}Rode[200010];

void Tarjan(int n)
{
    int j;
    Dfn[n]=Low[n]=++cnt;   
    Instack[n]=1;
    Stack[++top]=n;
    for(int i=first[n];i;i=next[i])
    {
        if(!Dfn[Rode[i].t])
        {
            Tarjan(Rode[i].t);
            Low[n]=min(Low[n],Low[Rode[i].t]);
        }
        else 
             if(Instack[Rode[i].t])  
                Low[n]=min(Low[n],Dfn[Rode[i].t]);
    }
    if(Dfn[n]==Low[n])
    {
        ++cnt_scc;
        do{
             j=Stack[top];
             Instack[j]=0;
             scc[cnt_scc].push_back(j);
             belong_scc[j]=cnt_scc;
             top--;
        }while(j!=n);
        for(int i=0;i<scc[cnt_scc].size();++i)         //統計環中的答案 
           for(int j=first[scc[cnt_scc][i]];j;j=next[j])
            if(belong_scc[Rode[j].t]==cnt_scc)
            {
                int p=Rode[j].d;
                double q=Rode[j].x;
                while(p)
                {
                   de_scc[cnt_scc]+=p;
                   p=(int)p*q;
                } 
          }
    }   
}
queue<int>q;
void SPFA()  //把每個環看做一個點 
{
    memset(de,-1,sizeof(de));
    q.push(belong_scc[S]);
    In[belong_scc[S]]=1;
    de[belong_scc[S]]=de_scc[belong_scc[S]];
    ans=max(ans,de[belong_scc[S]]);
    while(!q.empty())
    {
        int a=q.front();
        q.pop();
        In[a]=0;
        for(int i=0;i<scc[a].size();++i)
           for(int j=first[scc[a][i]];j;j=next[j])
              if(belong_scc[Rode[j].t]!=a&&de[belong_scc[Rode[j].t]]<(de[a]+de_scc[belong_scc[Rode[j].t]]+Rode[j].d))
              {
                  de[belong_scc[Rode[j].t]]=de[a]+de_scc[belong_scc[Rode[j].t]]+Rode[j].d;
                  ans=max(ans,de[belong_scc[Rode[j].t]]);
                  if(!In[belong_scc[Rode[j].t]])
                  {
                     q.push(belong_scc[Rode[j].t]);
                     In[belong_scc[Rode[j].t]]=1;
                  }
              }
    }
}
int main()
{
    freopen("mushroom.in","r",stdin);
    freopen("mushroom.out","w",stdout);
    scanf("%d%d",&N,&M);
    for(int i=1;i<=M;++i)
    {
       scanf("%d%d%d%lf",&Rode[i].f,&Rode[i].t,&Rode[i].d,&Rode[i].x);
       next[i]=first[Rode[i].f];
       first[Rode[i].f]=i;
    }
    scanf("%d",&S);
    Tarjan(S);
    SPFA();
    cout<<ans;
    return 0;
}

略長(-ω-;)


這裏寫圖片描述

T3(原題:洛谷 1709 隱藏口令)
注:數據範圍以數據範圍及提示中的爲準。

這個題,怎麼說呢,是個腦洞題??
(注意洛谷原數據是有分隔符的)

最暴力的打法: string 記錄當前最大答案,同時暴力枚舉以每個點開頭的字符串進行字典序的比較。(洛谷上貌似可以得61,但我們加強了數據,只能的40分 ,QAQQQ)

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

string a,b,c;
char k[100010];
int L,ans;

int main()
{
   scanf("%d",&L);
   while(scanf("%s",k)!=EOF)  a+=k;
   b=a;
   for(int i=1;i<=L;++i) 
   {
          c.assign(b,1,L-1); // 把b的後L-1位都複製給c
          c.push_back(b[0]);
          if(a>c) a=c,ans=i;
          b=c;
   }      
   printf("%d",ans); 
   return 0;
}

PS:爲了保護別人的思考成果,接下來的做法只給出思想,不貼出代碼_(:з」∠)_ 。

思考後的打法: (pingxing的%%%%%)
   我們發現最後的答案一定是以當前字符串中的最小的字母爲開頭的,於是我們用鏈表把最小的字母一一掛起,同時記錄他們的位置。同時記一個sl[p]表示已經比對到以鏈表中p元素開頭的字符串的第幾位。每次都把鏈表掃一遍,找出s[sl[p]]中最小的那個字符,並把第s[sl[p]]位大於這個字符的鏈表元素刪去(可以說是用了貪心的思想qwq)。當鏈表中只剩下一個元素是輸出答案。聽說dalao的鏈表在oj上會掛掉qwq,但是隨機數據是可以a掉的,並且在L>10^5時跑的比正解還快%%%。
   這就導致了一個很尷尬的情況,pingxing昨天下午一直在出數據想卡自己的鏈表,最後出了三組數據卡成了70(鏈表在aaaaaaaaabcd的情況下會掛,因爲都會被掛起來2333),最後成功的卡住了black(black打的是僞n^2的暴力,什麼鬼!!!∑(゚Д゚ノ)ノ,總之是卡掉了)。
  
正解 1· 再次思考後的打法

 既然最後的答案一定是以當前字符串中的最小的字母爲開頭的,每次的答案更新也只與當前位上更小的字母有關(貪心),那不如記兩個指針來記錄當前可能的最優答案。
 把原字符串複製一遍後,破環成鏈。
 令i=0,j=1,(i、j最大到L-1–>循環結束條件)作爲指向字符串開頭的兩個指針。另外記一個k表示當前已經比對到兩個字符串的第k個字符。每次操作:令 k=0 ,while s[i+k]==s[j+k]並且k<L(判斷越界),k++ 。直到跳到第一個不同的位置,比較大小。如果s[i+k]<s[j+k] 就說明j的答案不優,選擇把j往後跳,跳到j+k+1(貪心:因爲之前的k位已經比較過,s[j+k]比較大,那麼把j往後跳到j+k+1前的答案一定不是最優解)。反之,則跳i。
 情況處理:
 1.i==j 時 k會一直加到L,最後輸出錯誤答案。解決方法:++j
 2.如果k==L 直接輸出i,j中位置靠前的。(整個串都是一樣的字母 )
 3.如果最優答案只有一個,那麼i,j中一定會有一個跳到L,然後退出循環,這是輸出較小的那個就可以了。

正解 2 · 最終思考後的解法

別的與1一樣,就是把i固定指向最優字符串的,j指向當前的字符串。
先把k跳到這兩個字符串不相同的地方 ,如果k==L,就輸出i。
如果 S[i+k]< S[j+k] j=j+k+1;
不然
int l=i+k;
i=j; //把最優答案換爲j
j=max(j,l)+1; //j儘可能的往後跳。(這裏取max後會省略掉一些多餘的比較)

那這個題就完了,撒花(ノ≧∀≦)ノ ✿


這裏寫圖片描述
這裏寫圖片描述

題解當初打在代碼裏了qwq。

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

int n;
double a=4200,k,ans=0,x=100;

int main()
{
    freopen("water.in","r",stdin);
    freopen("water.out","w",stdout);
    scanf("%d",&n);
    k=(double) 1/n;
    ans+=a*x*k;
    for(int i=2;i<=n;++i)
    {
        x*=(double)(2*(i-1)-1)/(2*(i-1));
        ans+=a*x*k;
    }
    printf("%.2lf",ans);
    return 0;
} 

/*
最大的情況就是使已經燒開的水的熱量被儘可能的利用。
我們發現,當一杯水的熱量一個個的往右傳遞下去的話,熱量最不容易浪費。

** 熱量的傳遞 實際數據解釋: 
假設有5杯水: 0 0 0 0 0

第一杯水: 100 0 0 0 0 -->  6.25 50 25 12.5 6.25
第二杯水: 6.25 100 25 12.5 6.25--> 6.25 21.875 62.5 37.5 21.875
第三杯水: 6.25 21.875 100 37.5 21.875-->6.25 21.875 45.3125 68.75 45.3125
第四杯水: 6.25 21.875 45.3125 100 45.3125--> 6.25 21.875 45.3125 72.65625 72.65625
第五杯水:...... 100 。

我們發現 這五杯水被燒開前只進行熱傳遞可以達到的溫度爲  0 50 62.5 68.75 72.65625
還需要升高的溫度爲: 100 50 37.5 31.25 27.34375
發現: 50/100=1/2 、37.5/50=3/4 、31.25/37.5=5/6、27.34375/31.25=7/8 
規律:第i杯水需要上升的溫度爲第i-1杯水需要上升的溫度* (2*(i-1)-1)/(2*(i-1)). 

**熱量的傳遞 公式解釋(摘自洛谷題解) :

推導:設沸騰溫度爲a
//則第一杯溫度爲a,需要加熱t1=a 
//第二杯可以中和的最高溫度爲a/2,需要加熱t2=a/2 
//第三杯可以中和的最高溫度爲t3=(a/4+a)/2=5a/8,需要加熱t3=3a/8 
//第四杯可以中和的最高溫度爲t4=((a/8+5a/8)/2+a)/2=11a/16,需要加熱t4=5/16 
//則t3/t2=3/4=1-1/4,  t4/t3=5/6=1-1/6
//繼續推導得t(n+1)/t(n)=1-1/2n;

最後遞推求解。 

*/

終於完了23333(灬°ω°灬)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章