- 怀着国庆假都过不好,被拉去考试的深深怨念,我还是来写总结了……orz
T1 购物 shopping
题目描述
货架上有 N 种不同的商品,第 i 种商品质量为 ai 。
在购买至少一件商品的购买方案中,商品质量和第 K 小的购买方案的质量和是多少。
两种购买方案不同当且仅当选择购买的商品是不同的。
【输入格式】
输入文件名为 shopping.in。
第一行两个用空格分离的正整数 N 和 K。
第二行两 N 个用空格分离的正整数,第 i 个数表示 ai 。
【输出格式】
输出文件名为 shopping.out。
输出共一行,一个正整数,表示商品质量和第 K 小的购买方案的质量和。
【输入输出样例 1】
shopping.in
shopping.out
5 4
5 4 1 2 3
3
这道题……dalao说想到了正解还是比较简单,可惜比赛时我没想到……orz
巨菜QAQ
正解:先二分答案,然后暴力搜索验证,搜索枚举出来的元素超过K个就直接退出。
时间复杂度:KlogN
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#include<deque>
#include<cmath>
#include<cctype>
#define LL long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn=200000+50;
LL t[maxn],sum;
int a[maxn],n,tp,k;
int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
bool check(LL x)
{
LL cur = 0;
t[tp=1] = 0;
for (int i=1;i<=n;i++)
{
while (tp > 0 && t[tp]+a[i] > x) tp--;
if (!tp) return 0;
for (int j=1;j<=tp;j++) t[j+tp] = a[i] + t[j], cur++;
if (cur >= k) return 1;
tp <<= 1;
sort(t+1,t+tp+1);
}
return 0;
}
void solve()
{
sort(a+1,a+n+1);
LL L = 1, R = sum;
while (L < R)
{
LL mid = (L+R) >> 1;
if (check(mid)) R = mid;
else L = mid+1;
}
printf("%lld\n",L);
return ;
}
int main()
{
n=read(),k=read();
for (int i=1;i<=n;i++) a[i]=read(), sum+=a[i];
solve();
return 0;
}
T2 排列交换swap
题目描述
给定一个长度为n的排列,以及一个正整数K。
你可以进行如下操作若干次使得排列的字典序尽量小。
对于两个满足|i-j|≥K且|Pi-Pj|=1的下标i和j,交换Pi和Pj。
【输入格式】
输入文件名为 swap.in。
第一行两个用空格分离的正整数N和K。
第二行两N个用空格分离的正整数,第i个数表示Pi 。
【输出格式】
输出文件名为 swap.out。
输出共N行,输出排列字典序最小的排列,每行一个整数表示新排列的第i项。
【输入输出样例 1】
swap.in
swap.out
4 2
4 2 3 1
2
1
4
3
题意很好理解,第一反应就想暴力……
好吧,看一眼数据范围,还是老老实实一点吧
正解:读入 p[i],转化为: q[p[i]]=i,q[i]表示值为i的位置在哪。
考虑对于q[i]和q[j],若 |q[i]-q[j]|<k,(相距小于k),则q[i]和q[j]始终不能互换位置。,于是q[i]向q[j](i>j)连一条有向边即可,这样就构成了一个有向无环图。
将入度为0的点加入一个小根堆,做一遍拓扑排序。倒着枚举i,每次从堆里取出一个编号最小的x,使 p[i]=x,再将x连出的点入度减一;为0,则加入堆中。最后使q[p[i]]=i, 再将q输出即可。
为什么要维护一个堆呢?因为如果两点连了边(不能位置互换),就不会同时存在于堆中。在队中的点,肯定是可以位置互换的,那肯定是将小的数填在位置小那里,才能保证字典序最小。
又由于一个点只需与离它最近的点连边,维护一个维护最小值的线段树,倒着枚举i,每次查询 [q[i]-k+1,q[i]]和 [q[i],q[i]+k-1]两个区间的最小值即距离最近的两个点(一大一小),连边后再将q[i]这个位置单点修改成编号i即可.
是8是很容易?蓝鹅我考试时数组只开了两倍,其实应该开4倍的……所以光荣的RE了,只拿了55otz
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#include<deque>
#include<cmath>
#include<cctype>
#define LL long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
//我选择死亡
const int maxn=500000+5;
int n,k;
int tot=0;
int in[maxn];
int a[maxn],b[maxn];//p,q
int nxt[maxn*4],to[maxn*4];//^
int tree[maxn*4],head[maxn*4];
priority_queue<int,vector<int>,less<int> >q;
//堆
int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void addedge(int u,int v)
{
nxt[++tot]=head[u];
head[u]=tot;
to[tot]=v;
in[v]++;
}
void build(int l,int r,int rt,int x,int y)
{
if(l==r)
{
tree[rt]=y;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) build(lson,x,y);
else build(rson,x,y);
tree[rt]=min(tree[rt<<1],tree[rt<<1|1]);
}
int query(int l,int r,int rt,int x,int y)
{
if(x<=l && r<=y) return tree[rt];
int mid=(l+r)>>1;
if(y<=mid) return query(lson,x,y);
if(x>mid) return query(rson,x,y);
return min(query(lson,x,mid),query(rson,mid+1,y));
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) b[a[i]=read()]=i;;
memset(tree,60,sizeof(tree));
for(int i=n;i;i--)
{
int x=query(1,n,1,b[i]-k+1,b[i]);
if(x<=n) addedge(b[x],b[i]);
int y=query(1,n,1,b[i],b[i]+k-1);
if(y<=n) addedge(b[y],b[i]);
build(1,n,1,b[i],i);
}
for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
for(int i=n;i;i--)
{
a[i]=q.top();
q.pop();
for(int j=head[a[i]];j;j=nxt[j])
{
int v=to[j];
if(!--in[v])
{
q.push(v);
}
}
}
for(int i=1;i<=n;i++) b[a[i]]=i;
for(int i=1;i<=n;i++) printf("%d\n",b[i]);
return 0;
}
T3 收益 petrol
概率dp;
随便搞一下就好了;
正解:
设f[x]和g[x]表示用户给x元的概率以及期望的分红。
那么显然答案就是;
算f[x]和g[x]的时候,我们可以用背包dp,假设f[i][x],g[i][x]表示考虑了前i个人用户给了x元,对应的概率与期望分红。那么有
f[i][x]=f[i-1][x-m[i]]*p[i]+f[i-1][x]*(1-p[i])
g[i][x]=(g[i-1][x]*(1-p[i])+(g[i-1][x-m[i]]+f[i-1][x-m[i]]*m[i]*(r[i]+1)))*p[i]
我自己那份代码太丑了,没好意思拿上来放,就传了一份老师发的标程
#include<bits/stdc++.h>
using namespace std;
#define MAXN 105
#define ll long long
#define P 1000000007
int n,m,x,y,i,j,ans,a[MAXN],b[MAXN],c[MAXN],f[500005],g[500005];
int main()
{
scanf("%d%d%d",&n,&x,&y);
for(i=1;i<=n;i++)
{
scanf("%d%d%d",a+i,b+i,c+i);
b[i]=b[i]*570000004LL%P;
c[i]=c[i]*570000004LL%P;
m+=a[i];
}
for(f[0]=i=1;i<=n;i++)for(j=m;~j;j--)if(j>=a[i])
{
g[j]=((ll)(P+1-c[i])*g[j]+(g[j-a[i]]+(ll)f[j-a[i]]*a[i]%P*(b[i]+1))%P*c[i])%P;
f[j]=((ll)(P+1-c[i])*f[j]+(ll)c[i]*f[j-a[i]])%P;
}
else
{
g[j]=(ll)(P+1-c[i])*g[j]%P;
f[j]=(ll)(P+1-c[i])*f[j]%P;
}
for(i=x;i<=m;i++)ans=(ans+P-g[i]+(ll)f[i]*(y+i))%P;
cout<<ans<<endl;
return 0;
}
唉,昨天的还没总结……QAQ事情贼多
这次考试比较……嗯,不尽人意,小bug很多,像今天数组开小了丢了一百多分都是很不应该的
顺便膜一下AK的付大佬%%%%%%