您的位置:首页 > 其它

<队内胡策>2017.10.18 (DP+tarjan、SPFA+字符串+脑洞、数学)

2017-10-18 16:01 375 查看
*这可是我们组自己从网上找题出的一套考试题,虽然有些水,但是个人认为还是很不错啦(〃’▽’〃)!!尤其是我的题面(≖ᴗ≖๑)





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
[1],ans
[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
[1]<<" "<<dp
[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
=Low
=++cnt;
Instack
=1;
Stack[++top]=n;
for(int i=first
;i;i=next[i])
{
if(!Dfn[Rode[i].t])
{
Tarjan(Rode[i].t);
Low
=min(Low
,Low[Rode[i].t]);
}
else
if(Instack[Rode[i].t])
Low
=min(Low
,Dfn[Rode[i].t]);
}
if(Dfn
==Low
)
{
++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(灬°ω°灬)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  exam tarjan dp 脑洞