比赛时间:2018.10.15 选手:lrllrl 用时:2h 得分:100+10+90=200
最初想法是一个背包问题。首先背包问题的模型肯定是不行的,但是我们可以列出状态转移方程后发现,每个状态决策会对它之后的决策产生后效性。所以我们就倒着来考虑。考虑最终的结果会是怎么求出来的(假设只有
a
a
a,
b
b
b 同理):
a
n
s
=
(
w
×
a
i
1
+
w
×
Δ
k
×
a
i
2
+
⋯
+
w
×
Δ
k
m
−
1
×
a
i
m
)
ans=(w\times a_{i_1}+w\times\Delta k\times a_{i_2}+\cdots+w\times\Delta k^{m-1}\times a_{i_m})
ans=(w×ai1+w×Δk×ai2+⋯+w×Δkm−1×aim)
变形为我们倒序的情况:
a
n
s
=
w
×
(
Δ
k
m
−
1
×
a
i
m
+
⋯
+
Δ
k
×
a
i
2
+
a
i
1
)
ans=w\times(\Delta k^{m-1}\times a_{i_m}+\cdots+\Delta k\times a_{i_2}+a_{i_1})
ans=w×(Δkm−1×aim+⋯+Δk×ai2+ai1)
我们发现这样没走一步对后面没有影响,所以尽量让当前最大就行了。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int n,k,c,w,x[N],type[N];
double deltak,deltac,ans=0;
int main()
{
//freopen("explo.in","r",stdin);
//freopen("explo.out","w",stdout);
scanf("%d%d%d%d",&n,&k,&c,&w);
deltak=1.0-0.01*k;
deltac=1.0+0.01*c;
for(int i=1;i<=n;i++)
scanf("%d%d",&type[i],&x[i]);
for(int i=n;i;i--)
if(type[i]==1)ans=max(ans,ans*deltak+x[i]);
else ans=max(ans,ans*deltac-x[i]);
printf("%.2f\n",ans*w);
return 0;
}
根据抽屉原理,当询问区间长度大于
p
p
p 的时候一定存在一段子区间的和能被
p
p
p 整除的情况。
考虑小于
p
p
p 的长度,我们记录前缀和,O(n)扫描前缀和的同时查找曾经出现过的与其最相近的前缀和。作差取最小值。
正解是写treap或splay或替罪羊树啥的,我这里set居然过了也是神奇。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
const int N=5e5+10;
typedef long long ll;
const ll INF=0x7fffffff;
template<class T>inline void read(T&x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
int n,m,l,r,p,a[N];
ll sum[N];
int main()
{
//freopen("in.txt","r",stdin);
read(n);read(m);
for(int i=1;i<=n;i++)
read(a[i]),sum[i]=sum[i-1]+a[i];
while(m--)
{
read(l);read(r);read(p);int ans=p;
if(r-l+1>=p){puts("0");continue;}
set<int>s;s.clear();s.insert(0);
for(int i=l;i<=r;i++)
{
int tmp=(sum[i]-sum[l-1])%p;
set<int>::iterator it=s.upper_bound(tmp);
if(it!=s.begin())it--;
ans=min(ans,tmp-*it);
s.insert(tmp);
}
printf("%d\n",ans);
}
return 0;
}
这题居然是T3,让人感到惊奇,一看就是个二分+spfa。准备就这么过掉时突然想到一种情况,如果一个负环与
n
n
n 点不连通,可能会花费许多无用的值去使它变成非负环。于是先建反图深搜一遍打个标记再建图二分跑spfa。(于是我判断1号点是否连通写错了,90分qwq)
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=110,M=1e5+10;
int n,m,hd[N],vis[N],tot,t,cnt[N],avl[N];
ll dis[N],ans;
struct Edge{
int v,w,nx;
}e[M];
struct edge{
int u,v,w;
}tmp[M];
void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].nx=hd[u];
hd[u]=tot;
}
ll spfa(int mid)
{
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
memset(cnt,0,sizeof(cnt));
queue<ll>q;while(q.size())q.pop();
q.push(1);vis[1]=1;dis[1]=0;
while(q.size())
{
int u=q.front();q.pop();vis[u]=0;
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;if(!avl[v])continue;
if(dis[v]>dis[u]+e[i].w+mid)
{
dis[v]=dis[u]+e[i].w+mid;
if(!vis[v])
{
q.push(v);
if(++cnt[v]>=n)return -0x3f3f3f3f;
}
}
}
}
return dis[n];
}
void dfs(int u){avl[u]=1;for(int i=hd[u];i;i=e[i].nx)if(!avl[e[i].v])dfs(e[i].v);}
int main()
{
//freopen("earth.in","r",stdin);
//freopen("earth.out","w",stdout);
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
memset(hd,0,sizeof(hd));tot=0;
memset(avl,0,sizeof(avl));
for(int i=1;i<=m;i++)
scanf("%d%d%d",&tmp[i].u,&tmp[i].v,&tmp[i].w);
for(int i=1;i<=m;i++)
add(tmp[i].v,tmp[i].u,tmp[i].w);
dfs(n);
memset(hd,0,sizeof(hd));tot=0;
for(int i=1;i<=m;i++)
add(tmp[i].u,tmp[i].v,tmp[i].w);
int l=-100000,r=100000,mid;
if(!avl[1]){printf("-1\n");continue;}
while(l<=r)
{
int mid=(l+r)/2;ll dist=spfa(mid);
if(dist>=0)ans=dist,r=mid-1;
else l=mid+1;
}
printf("%lld\n",ans);
}
return 0;
}
赛后总结
问题有很多:
1.T1没有很快的反应过来倒序消除后效性再贪心的每步去最大值,足足浪费50分钟。
2.T2的暴力居然只打了十分。而且没有想到可以固定左端点二分右端点,思路没有打开,没想清楚根据前缀和作差可以考虑到任何一个子区间。只要想到这点,即便甩个set上去也能AC
3.T3的判断连通瞎姬霸写,又丢掉10分。
分就是这样一点一点扣下去的,扣着扣着就是1=—>2=。吸取教训。