@CQUPTacm
2018-04-22T19:49:44.000000Z
字数 8336
阅读 1676
题解
计算一下会过热多少次,然后加上这么多次的休息时间即可。
答案会爆int所以要用long long。
抵达终点的瞬间如果过热了,那次休息时间不用算入总时间。
时间复杂度o(T),空间复杂度o(1)。
代码
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int t;
long long l,e,r;
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%lld",&l,&e,&r);
printf("%lld\n",l+((l-1)/e)*r);
}
return 0;
}
英文题,题意是N+1个人中速度前M快的会被劝退,速度相同的人中你是最后被劝退的。问你不被劝退的最高速度。
把N个人的速度排序,你的速度最快可以和第M快的人相同。排序要求复杂度为nlogn级别以下。
注意两种特殊情况:M>N的时候你肯定会被劝退,M=0的时候你可以跑到极速。
时间复杂度o(T*N*log(N)),空间复杂度o(N)。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
double v[10005];
int main()
{
int t,n,m;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%lf",&v[i]);
v[n]=2017211799;
sort(v,v+n+1);
if(m>n)
printf("GG\n");
else
printf("%.2f\n",v[n-m]);
}
return 0;
}
英文题,题意为N次攻击,你可以防御其中连续的不超过M次攻击,问剩下的伤害值的和最小是多少。
求一个长度不超过M的区间,让这个区间内元素的和最大。用总和减去这个区间的和就是最后答案。因为元素都是正数,所以当M<N时,和最大的区间长度一定是M。我们可以枚举区间左端点l,那么区间右端点r是l+M-1。区间l到r的和可以转化为两个前缀和的差,即区间1到r的和减去区间1到l-1的和。
标程给的是另一种方法:先算出前M个元素的和,然后每次加上新的一个元素,把最早的一个元素去掉,动态维护当前区间和与之前最大区间和的更大值。
M>=N的时候,所有攻击都被抵御。
时间复杂度o(T*N),空间复杂度o(N)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int num[100005];
int main()
{
int t,n,m;
long long ans,temp,maxx;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
ans=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&num[i]);
ans+=num[i];
}
if(m>=n)
printf("0\n");
else
{
temp=0;
for(int i=1;i<=m;i++)
temp+=num[i];
maxx=temp;
for(int i=m+1;i<=n;i++)
{
temp+=num[i];
temp-=num[i-m];
maxx=max(maxx,temp);
}
printf("%lld\n",ans-maxx);
}
}
return 0;
}
这题难度偏高。我们考虑一部分用原有的金币支付,一部分用复制币支付,那么原有金币支付的那部分实际上是多重背包判可行性,而复制币支付的那部分是一个完全背包。完全背包的部分不需要多说,多重背包的部分需要二进制优化。
注意要先跑两种背包,再枚举用原有金币的部分有多少。
时间复杂度o(T*N*log(C)*M),空间复杂度o(M+N*log(C))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
bool ok[10005];
int ans[10005];
int v[15],c[15],num[105];
int main()
{
int t,n,m,minn,cnt,temp,now;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&v[i],&c[i]);
now=1;
while(c[i])
{
if(c[i]>=now)
{
c[i]-=now;
cnt++;
num[cnt]=now*v[i];
}
else
{
cnt++;
num[cnt]=c[i]*v[i];
c[i]=0;
}
now*=2;
}
}
for(int i=0;i<=m;i++)
{
ok[i]=0;
ans[i]=10086;
}
ok[0]=1;
ans[0]=0;
minn=10086;
for(int i=1;i<=cnt;i++)
for(int j=m;j>=num[i];j--)
ok[j]|=ok[j-num[i]];
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
ans[j]=min(ans[j],ans[j-v[i]]+1);
for(int i=0;i<=m;i++)
{
if(ok[i])
minn=min(minn,ans[m-i]);
}
if(minn==10086)
printf("-1\n");
else
printf("%d\n",minn);
}
return 0;
}
这题难度偏高。首先我们可以用单词建一棵字典树,树上的点可以通过加字母的方式走到它的子节点。我们用ok[i][0]为1表示当我们走到i节点的时候想赢就能赢,用ok[i][1]为1表示当我们走到i节点的时候想输就能输。那么,节点的情况分四种:想赢就赢想输就输,想赢能赢想输看对面,想输能输想赢看对面,输赢都看对面。我们能分析出:如果我的对手在i的所有子节点都不能靠自己赢,那么我在i能靠自己赢;如果我的对手在i的所有子节点都不能靠自己输,那么我在i能靠自己输。从没有子节点的点即字典树上各条链的结尾倒推,初始状态为没有子节点的点想输能输想赢看对面。
如果从空串的情况开始,先手的想赢能赢想输能输,那么他可以先输K-1轮,把先手一直放在自己这,最后一轮取胜。如果先手的想赢能赢想输看对面,那么如果K是奇数,每轮都是先手赢对我有利,那么在我后手的时候我肯定不会让先手的输,反之对手也会在后手的时候故意输给我,所以最终每局都是先手胜利,所以K为奇数我赢反之对面赢。如果先手想输能输想赢看对面,那么对面只要一直让我输,保持我先手,我就输定了。如果先手的输赢都看对面,那么对面同样只要一直让我输,我就输定了。
时间复杂度o(T*N),空间复杂度o(N)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct Node
{
bool ok[2];
int son[26];
}p[200005];
int cnt;
char s[200005];
void newnode()
{
cnt++;
p[cnt].ok[0]=0;
p[cnt].ok[1]=0;
for(int i=0;i<26;i++)
p[cnt].son[i]=0;
}
void add(int x,int i)
{
if(!s[i])
return;
s[i]-='a';
if(!p[x].son[s[i]])
{
newnode();
p[x].son[s[i]]=cnt;
}
add(p[x].son[s[i]],i+1);
}
void dfs(int x)
{
bool flag=0;
for(int i=0;i<26;i++)
{
if(p[x].son[i])
{
flag=1;
dfs(p[x].son[i]);
if(!p[p[x].son[i]].ok[1])
p[x].ok[1]=1;
if(!p[p[x].son[i]].ok[0])
p[x].ok[0]=1;
}
}
if(!flag)
p[x].ok[1]=1;
}
int main()
{
int t;
int n,k;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
cnt=-1;
newnode();
while(n--)
{
scanf("%s",s);
add(0,0);
}
dfs(0);
if((p[0].ok[0] && p[0].ok[1]) || (p[0].ok[0] && (k%2)))
printf("Pass\n");
else
printf("Fail\n");
}
return 0;
}
这题难度是最高的。如果没有王の键的设定,那么这道题实际上就是一个最小生成树。加上王の键的设定之后,实际上就变成了求包含指定边的最小生成树。我们考虑最小生成树加上指定边之后,会形成一个环,环上除了指定边之外的边,可以砍掉一条。为了让生成树最小,我们会砍掉环上最大的那条边。于是问题就变成了,在原本的生成树上,从点U到点V的路上最大的那条边是多少。这个可以用LCA解决。
当指定边在原本的生成树上时,两点之间只有一条边,相当于砍掉和加上同一条边,不影响答案。
把K次询问的答案加起来就是最后的答案。输出会爆int,要用long long。
时间复杂度o(T*(M*log(M)+K*log(N))),空间复杂度o(N*log(N)+M)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
struct Edge
{
int u,v,e;
bool friend operator <(Edge x1,Edge x2)
{
return x1.e<x2.e;
}
}edges[100005];
struct E
{
int to,e;
}temp;
int u[100005],v[100005],e[100005];
int root[10005];
int f[10005],pre[10005][15],maxx[10005][15];
vector <E> vec[10005];
int findr(int x)
{
if(root[x]==x)
return x;
else
return root[x]=findr(root[x]);
}
void dfs(int x,int fa,int nowe)
{
f[x]=f[fa]+1;
pre[x][0]=fa;
maxx[x][0]=nowe;
for(int i=1;i<15;i++)
{
pre[x][i]=pre[pre[x][i-1]][i-1];
maxx[x][i]=max(maxx[x][i-1],maxx[pre[x][i-1]][i-1]);
}
for(int i=0;i<vec[x].size();i++)
{
if(vec[x][i].to!=fa)
dfs(vec[x][i].to,x,vec[x][i].e);
}
}
int lca(int x,int y)
{
while(f[x]>f[y])
{
for(int i=14;i>=0;i--)
{
if(f[pre[x][i]]>f[y])
x=pre[x][i];
}
x=pre[x][0];
}
while(f[y]>f[x])
{
for(int i=14;i>=0;i--)
{
if(f[pre[y][i]]>f[x])
y=pre[y][i];
}
y=pre[y][0];
}
while(x!=y)
{
for(int i=14;i>=0;i--)
{
if(pre[y][i]!=pre[x][i])
{
y=pre[y][i];
x=pre[x][i];
}
}
x=pre[x][0];
y=pre[y][0];
}
return x;
}
int ask(int x,int y)
{
int now=0;
while(f[y]>f[x])
{
for(int i=14;i>=0;i--)
{
if(f[pre[y][i]]>f[x])
{
now=max(now,maxx[y][i]);
y=pre[y][i];
}
}
now=max(now,maxx[y][0]);
y=pre[y][0];
}
return now;
}
int main()
{
int t,n,m,k,x,y,now;
long long ans,sum;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&m,&n,&k);
for(int i=1;i<=n;i++)
{
vec[i].clear();
root[i]=i;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&edges[i].u,&edges[i].v,&edges[i].e);
u[i]=edges[i].u;
v[i]=edges[i].v;
e[i]=edges[i].e;
}
sort(edges+1,edges+1+m);
ans=0;
sum=0;
for(int i=1;i<=m;i++)
{
x=findr(edges[i].u);
y=findr(edges[i].v);
if(x!=y)
{
sum+=edges[i].e;
root[x]=y;
temp.e=edges[i].e;
temp.to=edges[i].u;
vec[edges[i].v].push_back(temp);
temp.to=edges[i].v;
vec[edges[i].u].push_back(temp);
}
}
dfs(1,0,0);
while(k--)
{
scanf("%d",&now);
ans+=e[now];
x=u[now];
y=v[now];
now=lca(x,y);
ans+=sum-max(ask(now,x),ask(now,y));
}
printf("%lld\n",ans);
}
return 0;
}
无法过桥的情况只有一种:某些地雷连成一片,并且分别和桥的宽度方向的两个边界有接触。所以我们只需要枚举任意两个地雷,看他们是否有接触,如果有就连一条边。同时枚举地雷考虑和左右边界有没有接触。最后跑个搜索染色看看左右边界是否连在一起即可。
时间复杂度o(T*N*N),空间复杂度o(N*N)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
double x[105],y[105],r[105];
bool maps[105][105];
bool vis[105];
int n;
bool ok(int a,int b)
{
return (r[a]+r[b])*(r[a]+r[b])>=(x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]);
}
void dfs(int x)
{
vis[x]=1;
for(int i=0;i<=n+1;i++)
{
if(maps[x][i] && !vis[i])
{
dfs(i);
}
}
}
int main()
{
int t;
double l,w;
scanf("%d",&t);
while(t--)
{
scanf("%lf%lf%d",&w,&l,&n);
for(int i=1;i<=n;i++)
scanf("%lf%lf%lf",&x[i],&y[i],&r[i]);
memset(maps,0,sizeof(maps));
memset(vis,0,sizeof(vis));
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
{
if(ok(i,j))
maps[i][j]=maps[j][i]=1;
}
for(int i=1;i<=n;i++)
{
if(x[i]<=r[i])
maps[0][i]=maps[i][0]=1;
if(x[i]+r[i]>=w)
maps[n+1][i]=maps[i][n+1]=1;
}
dfs(0);
if(vis[n+1])
printf("GG\n");
else
printf("Nice\n");
}
return 0;
}
枚举普通果苣的数目i,剩下的M-i种都是黑暗果苣,保证M-i<K的情况下,答案实际上是C(N1-N2,i)*C(N2,M-i)。把每种情况对应的答案加起来就是最后的答案。
求组合数可以用C(N,M)=C(N-1,M-1)+C(N-1,M)的公式,题解给的是另一种办法:C(N,M)=N!/(M!*(N-M)!)=(N*(N-1)*...*(N-M+1))/(1*2*...M)。不过这样算会爆long long,所以我们要用N/1*(N-1)/2*(N-2)/3...*(N-M+1)/M。
当N<M的时候C(N,M)=0。
时间复杂度o(T*K*K),空间复杂度o(1)。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long com(int n,int m)
{
long long now=1;
if(m>n)
return 0;
for(int i=1;i<=m;i++)
{
now=now*n;
n--;
now=now/i;
}
return now;
}
int main()
{
int t,n1,n2,m,k;
long long ans;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d",&n1,&n2,&m,&k);
ans=0;
for(int i=0;i<=m;i++)
{
if(m-i<k)
ans+=com(n1-n2,i)*com(n2,m-i);
}
printf("%lld\n",ans);
}
return 0;
}
一段连续的“O”可以毫无代价的走来走去,以“X”分隔开的两段“O”只能以跳跃的方式互相到达。先处理出每段连续的“O”的左端点和右端点,然后枚举两段“O”判断能不能跳到,能跳到就连一条无向边,最后求从含1的那段“O”到含N的那段“O”的最小距离,可以用bfs 解决。
时间复杂度o(T*N*N),空间复杂度o(N*N)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
char s[1005];
int ans[1005];
bool ok[1005][1005];
int l[1005],r[1005];
queue <int> q;
int main()
{
int t,n,m,temp,cnt;
bool flag;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
flag=0;
cnt=0;
for(int i=1;i<=n;i++)
{
if(s[i]=='O')
{
if(!flag)
{
cnt++;
l[cnt]=i;
flag=1;
}
}
else
{
if(flag)
{
r[cnt]=i-1;
flag=0;
}
}
}
if(flag)
r[cnt]=n;
for(int i=1;i<=cnt;i++)
{
ans[i]=-1;
for(int j=1;j<=cnt;j++)
ok[i][j]=0;
}
for(int i=1;i<cnt;i++)
for(int j=i+1;j<=cnt;j++)
{
if(l[i]+m+1<=r[j] && r[i]+m+1>=l[j])
ok[i][j]=ok[j][i]=1;
}
ans[1]=0;
q.push(1);
while(!q.empty())
{
temp=q.front();
q.pop();
for(int i=1;i<=cnt;i++)
{
if(ans[i]==-1 && ok[temp][i])
{
ans[i]=ans[temp]+1;
q.push(i);
}
}
}
printf("%d\n",ans[cnt]);
}
return 0;
}