昨天考的題,今天早晨才把T2調完,竟然卡了精度
原題鏈接:
T1:gjh自己出的(靈感來源:codevs 1742 爬樓梯 鏈接)
T2:luogu 2656 採蘑菇 鏈接
T3:codevs 1456 隱藏口令 鏈接
T4:luogu 1984 燒水問題 鏈接
T1
第一問DP,第二問隨便打打貪心
結果我第二問貪心在模擬的過程中就打次了(第一次貪心模擬輸給了DP)
不多說 很簡單
代碼:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100000+10,mo=19260817;
int n,m;
int h[maxn],f[maxn],ans,cnt;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
h[x]=y;
}
for(int i=1;i<=n;++i)
if(!h[i]) h[i]=1;
f[1]=f[0]=1;
cnt=h[1];
ans=1;
for(int i=2;i<=n;++i)
{
if(cnt+h[i]<=4) cnt+=h[i];
else
{
cnt=h[i];
ans++;
}
int last=h[i];
int j;
for(j=i-1;j>=0;--j)
{
last+=h[j];
f[i]=(f[i]%mo+f[j]%mo)%mo;
if(last>4) break;
}
}
printf("%d %d",f[n],ans);
return 0;
}
T2:(模板題)
思路:tarjan縮點,處理每一個scc裏最大的蘑菇數量,跑最長路
現在說一下我哪裏被卡了….
在統計每一個scc裏面的蘑菇數量時,對於每一條邊,都要反覆採集直到數量變爲0
一開始,我的程序是這樣寫的:
int cal(int i)
{
int f=e[i].f,t=e[i].t;
int v=e[i].v;
int ret=v;
double k=e[i].k;
while(v)
{
ret+=v*k;
v*=k;
}
return ret;
}
死活wa了4個點,大寫的懵逼
後來我發現這樣寫可以過:
int cal(int i)
{
int f=e[i].f,t=e[i].t;
int v=e[i].v;
int ret=v;
double k=e[i].k;
while(v)
{
v*=k;
ret+=v;
}
return ret;
}
然後就更懵逼了
(內心:這倆程序有什麼不同??!!)
我把兩個程序單獨摘出來對拍。結果真拍出了一組不同的結果:
輸入:6840 0.7
輸出:第一種:21555 第二種:21554
我把兩個程序的計算流程輸出,發現有一個地方出現了問題:
中間有一個過程,需要答案+(20*0.7)
第一個程序,ans加了14,第二個,ans加了13
兩段程序的區別就是:
前者直接把ans加上v*k,後者是先把v*k賦值給一個int型變量,再把ans加上這個int變量。
後來知道20*0.7=14.0,double中下取整會變成13.0,但是始終不知道這兩種加法到底有什麼區別,按理說計算結果都應該是ans+=13
不過至少知道了14.0這種東西是以13.99999..存儲的,想把它下取整變成14的話,就加0.00005之類的
因爲這個調了很久
代碼:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
#include<queue>
using namespace std;
const int maxn1=80000+500,maxn2=200000+500;
int n,m,cnt,s,scc_cnt,Index,ans;
int fist[maxn1],nxt[maxn2],low[maxn1],dfn[maxn1],where[maxn1],num[maxn1];
int fi[maxn2],ti[maxn2],vi[maxn2],dis[maxn1];
double ki[maxn2];
vector<int>which[maxn1];
bool instack[maxn1],vis[maxn1];
struct hh
{
int f,t,v;
double k;
}e[maxn2];
queue<int>q;
stack<int>S;
void init()
{
memset(fist,-1,sizeof(fist));
memset(nxt,0,sizeof(nxt));
memset(dfn,-1,sizeof(dfn));
memset(vis,0,sizeof(vis));
cnt=0;
}
void build(int f,int t,int v,double k)
{
e[++cnt]=(hh){f,t,v,k};
nxt[cnt]=fist[f];
fist[f]=cnt;
}
int cal(int i)
{
int f=e[i].f,t=e[i].t;
int v=e[i].v;
int ret=v;
double k=e[i].k;
while(v)
{
v*=k;
ret+=v;
}
return ret;
}
void tarjan(int i)
{
dfn[i]=low[i]=++Index;
S.push(i);
for(int x=fist[i];x!=-1;x=nxt[x])
{
int j=e[x].t;
if(dfn[j]==-1)
{
tarjan(j);
low[i]=min(low[i],low[j]);
}
else if(!where[j]) low[i]=min(low[i],dfn[j]);
}
if(dfn[i]==low[i])
{
scc_cnt++;
int j;
do
{
j=S.top();
S.pop();
which[scc_cnt].push_back(j);
where[j]=scc_cnt;
}
while(j!=i);
}
}
void done()
{
for(int u=1;u<=n;++u)
{
for(int i=fist[u];i!=-1;i=nxt[i])
{
int v=e[i].t;
if(where[v]==where[u])
num[where[v]]+=cal(i);
}
}
init();
for(int i=1;i<=m;++i)
{
int f=fi[i],t=ti[i];
if(where[f]!=where[t])
build(where[f],where[t],vi[i],ki[i]);
}
}
void spfa(int ds)
{
vis[ds]=true;
q.push(ds);
dis[ds]=num[ds];
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=false;
for(int i=fist[u];i!=-1;i=nxt[i])
{
int v=e[i].t;
if(dis[v]<dis[u]+e[i].v+num[v])
{
dis[v]=dis[u]+e[i].v+num[v];
//e[i].v*=e[i].k;
if(!vis[v])
{
vis[v]=true;
q.push(v);
}
}
}
}
}
int main()
{
init();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d%d%d",&fi[i],&ti[i],&vi[i]);
cin>>ki[i];
build(fi[i],ti[i],vi[i],ki[i]);
}
scanf("%d",&s);
for(int i=1;i<=n;++i)
if(!where[i]) tarjan(i);
done();
spfa(where[s]);
for(int i=1;i<=scc_cnt;++i) ans=max(ans,dis[i]);
printf("%d",ans);
return 0;
}
T3:
考試的時候沒想到
其實直接兩兩比較就可以,兩個指針,i指當前找到的字典序最小的串的開頭字符,j指將要和它進行比較的字符串的開頭字符,每次指針往後移,再用一個k每次比較兩個指針往後k位的字符就可以了
一開始想的是:(僞代碼)
if(s[i+k]>s[j+k]) 說明當前比較的字符串比之前的最小字符串更優,所以 i=j,j++;
if(s[i+k]<s[j+k]) 說明當前字符串不如之前的最小字符串更優,所以 j++ 比較下一位
但是會TLE,而且TLE了65%
考慮優化
首先,如果每次指針j都往後只跳一個格子,肯定有冗雜的操作
對於s[i+k]>s[j+k]的情況,我們可以直接把j跳到i+k+1的地方,也就是說,對於以[i~i+k+1)這段區間,一定不會是答案。(如果j > i+k+1,直接跳到j,因爲我們要保證i始終在j的左邊,便於統計答案)
這裏不是很好想。
來一波證明:
即證明:對於以[i~i+k+1)這段區間開頭的字符串,一定存在某字符串的字典序比它小
設該區間中某一個字符i’,則對於字符串s[i’~i+k],長度len爲i+k-i’+1,則在區間[j~j+k+1),長度相同的以j’開頭的字符串s[j’~j+k],由於這兩段區間在比對到i+k和j+k之前比對時,已經確定,s[i’~i+k-1]一定等於s[j’~j+k-1],且s[i+k]>s[j+k],那麼s[i’~i+k]的字典序一定大於s[j’~j+k],所以s[i’~i+k]一定不是答案。
對於s[i+k]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,ans;
string ss,s;
int main()
{
scanf("%d",&n);
while(cin>>ss) s+=ss;
s+=s;
int i=0,j=1;
while(i<n&&j<n)
{
bool flg=0;
for(int k=0;k<n;++k)
{
if(s[i+k]>s[j+k])
{
int x=i;
i=j;
j=max(j,x+k)+1;
break;
}
else if(s[i+k]<s[j+k])
{
j=j+k+1;
break;
}
if(k==n-1)
{
flg=1;
break;
}
}
if(flg) break;
}
printf("%d",i);
return 0;
}
T4:
數學推理題,代碼賊短,懶得寫了,直接貼題解吧╮(╯▽╰)╭
最大的情況就是使已經燒開的水的熱量被儘可能的利用。 我們發現,當一杯水的熱量一個個的往右傳遞下去的話,熱量最不容易浪費。
熱量的傳遞 實際數據解釋: 假設有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 32.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;最後遞推求解。
代碼:
#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;
}