昨天考的题,今天早晨才把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;
}