A. Cheap Travel(水题)
题意:坐地铁,每次价格是a,有一种特殊票,每张价格为b,可以坐m次。求问坐n次的最少花费。
做法一般是两种,一种是直接枚举b的张数,取最小值,简单粗暴但有效,题目数据量不大。
当然,分析一下就可以发现,如果a*m > b,那么当次数有m次时,肯定是b划算。否则就全部买a的。
而如果买了多次b的之后,剩下n%m次不够买一次b,这时再判断下a*(n%m) 跟b的大小即可得到答案。
#include<cstdio>
int main(){
int n, m, a, b;
while(~scanf("%d %d %d %d", &n, &m, &a, &b)){
if(a*m > b){
int ans = (n/m)*b;
n%=m;
if(n*a <= b) ans += n*a;
else ans += b;
printf("%d\n", ans);
}
else{
printf("%d\n", n*a);
}
}
return 0;
}
B. Wonder Room(枚举)
题意:一个房间要容纳n个人,每个人要至少占用6平方米,现在房间是a*b大小,可以对a和b分别增大,问增大后能满足条件的房间的最小面积是多少。
根据题意可知房间最小面积是6*n,如果一开始a*b就大于等于6*n,不用修改直接输出答案。
对于要修改的,我是枚举a的大小,根据a求出b的最小长度,再算面积取最小值。
但是由于n<=10^9,全部枚举会超时,而当a超过sqrt(6*n)时,其实这时候b是很小的,所以当a超过sqrt(6*n),反过来就去枚举b。
枚举量最多就是2*sqrt(6*n)。
枚举的时候注意a和b不能比原来小就可以。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
int main(){
LL n, a, b, c, A, B;
while(~scanf("%I64d %I64d %I64d", &n, &a, &b)){
n*=6;
if(a*b>=n){
printf("%I64d\n%I64d %I64d\n", a*b, a, b);
}
else{
LL m = (LL)sqrt(n+0.1);
LL ans = 0x7fffffffffffffffLL;
for(LL i=a; i<=m && ans>n; i++){
if(n%i==0) c = n/i;
else c = n/i + 1;
if(c>=b){
if(i*c<ans){
ans = i*c;
A = i;
B = c;
}
}
}
for(LL i=b; i<=m && ans>n; i++){
if(n%i==0) c = n/i;
else c = n/i + 1;
if(c>=a){
if(i*c<ans){
ans = i*c;
A = c;
B = i;
}
}
}
printf("%I64d\n%I64d %I64d\n", ans, A, B);
}
}
return 0;
}
C. Number of Ways(前缀和,扫描)
题意:给定N个数的序列,对于2<=i<=j<=n-1,求出有多少组(i,j)满足sum[1, i-1],sum[i,j], sum[j+1, n]三段的和都相等。
因为三段加起来就是原来所有数的和,所以如果原先的和S不能被3整除,答案就是0,否则每段的和都应该是S/3。
我的做法是先从后往前叠加,我们先找个f[i],如果sum[i, n]等于S/3,那么标记f[i]为1,否则为0。
而cnt[i] = cnt[i+1] + f[i]。
然后再从前往后叠加,遇到sum[1, i]等于S/3的,就把cnt[i+2]加到答案。此时相当于到i为止作为第一段,然后在i+2到n里面寻找第三段的开始,剩下的就是第二段。
后来看了别人的代码,其实一次扫就可以,从左往右如果遇到sum[1, i]等于S/3的,就记录cnt[i] = cnt[i-1]+1,否则cnt[i]=cnt[i-1],遇到sum[1,i]等于S/3*2的,就把cnt[i-2]加进答案。思想还是一样的。
我的代码还是按照第一种做法来,即两边扫。
#include<cstdio>
#include<cstring>
typedef long long LL;
const int N = 500010;
int n;
LL a[N];
int cnt[N];
int main(){
while(~scanf("%d", &n)){
LL sum = 0;
memset(cnt, 0, sizeof(cnt));
for(int i=1; i<=n; i++){
scanf("%I64d", a+i);
sum += a[i];
}
if(sum%3!=0){
puts("0");
continue;
}
LL x = sum/3;
LL y = 0;
for(int i=n; i>0; i--){
y += a[i];
cnt[i] = cnt[i+1];
if(y==x) cnt[i]++;
}
LL ans = 0;
sum = 0;
for(int i=1; i<=n; i++){
sum += a[i];
if(sum==x){
ans += cnt[i+2];
}
}
printf("%I64d\n", ans);
}
return 0;
}
D. Increase Sequence(dp)
题意:题目描述一种对序列的操作,对于给定的[l, r],一次操作表示这个区间的值全部加上1。可以有多次操作,唯一的限制是对于任意的[li, ri]和[lj, rj],li!=lj, ri!=rj。
问的是有多少种方案使得所有数字都变成给定的h。两种方案只要有一个操作区间不同,就算不同方案,当然区间的操作顺序是无关的。
首先还是把明显不可达的情况去掉,如果序列当中已经有数字大于h了,肯定没有方案,输出0。
接下来就是dp的时候了。
dp[i][j]表示到第i个数字为止,1~i所有数字都变成h,并且此时还有j个区间覆盖到,即还要往后面作用。
那么,对于a[i],计算d=h-a[i],说明要有d个区间覆盖到a[i],有两种状态转移过来:
一种是直接前面的dp[i-1][d]过来,a[i]这里不增加新区间。
另一种当然就是前面的dp[i-1][d-1]过来,a[i]再增加新区间。
所以dp[i] = dp[i-1][d] + dp[i-1][d-1]。
然后,由于我们可以选择在a[i]这里结束一个区间,有d个区间可以选择,所以还有dp[i][d-1] = dp[i][d] * d。
最后的答案就是dp[n][0],表示所有数字都变成h,而且操作的区间都结束掉了。
初始状态dp[0][0]=1,其它清0。
#include<cstdio>
#include<cstring>
typedef long long LL;
const int mod = 1000000007;
int n, h, a[2001];
LL dp[2001][2000];
int main(){
while(~scanf("%d %d", &n, &h)){
bool flag = 0;
for(int i=1; i<=n; i++){
scanf("%d", a+i);
if(a[i]>h) flag = 1;
}
if(flag){
puts("0");
continue;
}
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int i=1; i<=n; i++){
int d = h-a[i];
dp[i][d] = dp[i-1][d];
if(d){
dp[i][d] = (dp[i][d] + dp[i-1][d-1])%mod;
dp[i][d-1] = dp[i][d] * d % mod;
}
}
printf("%I64d\n", dp[n][0]);
}
return 0;
}
E. Information Graph(并查集+lca+离线处理)
题意:n个员工,一开始没有上下关系。然后m次询问,询问分三种:
1 x y,y成为x的上司,题目保证在此之前x没有上司。
2 x,给x一份文档,他签名,然后给他的上司签名,上司签完再给上司的上司,直到没有上司了。
3 x i,询问x是否给编号i的文档签过名。文档编号是按照询问2出现的顺序从1开始标的。
昨晚做的时候考虑欠缺,system test挂掉了。
由于上下关系只有添加没有删减,所以我们可以先把所有询问读进来,顺便把上下关系构建起来。这种关系构成的可能是树,也可能是森林,我们可以增加一个虚拟节点0,让那些还没上司的当0为上司,这样就转化成树了。
完成之后我们才正式来处理m个询问。
那么,对于一份由x开始签名的文档,如果y签过名,y肯定在x向根走的路径上,y必须是x的祖先。所以用lca(y, x)是否等于y来做这个判断。
但是这里有一个问题,如果y是x签过某份文档之后,再成为x的上司,y是不会签到这份文档的。处理也很简单,增加个并查集,判断下他们在文档签完名是否已经在同一组即可。
另外,由于文档签名了之后信息是不会改变的,所以每处理一个询问2,就顺便把对应该文档的询问3处理掉,这样也不用担心y在x签完文档之后才成为x的上司的问题。
最后再按询问的顺序把询问3输出。
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int LOG = 20;
const int N = 100010;
#define pb push_back
struct Query{
int id, x;
Query(){}
Query(int id, int x):id(id),x(x){}
};
vector<Query> Q[N];
vector<int> V[N];
inline void in(int &x){
x=0;
char c=getchar();
while(c<48 || c>57) c=getchar();
while(c>=48 && c<=57){
x = x*10+c-48;
c = getchar();
}
}
int n, m, q;
int t[N], x[N], y[N], ans[N], f[N];
int find(int X){
int Y = X;
for(; X!=f[X]; X=f[X]);
return f[Y]=X;
}
bool mk[N];
int parent[LOG][N];
int depth[N];
void dfs(int x, int p, int d){
parent[0][x] = p;
depth[x] = d;
for(int i=0; i<V[x].size(); i++){
if(V[x][i] != p) dfs(V[x][i], x, d+1);
}
}
void init(){
for(int i=1; i<=n; i++){
if(mk[i]){
V[0].pb(i);
}
}
dfs(0, -1, 0);
for(int k=0; k+1<LOG; k++){
for(int i=0; i<=n; i++){
if(parent[k][i]<0) parent[k+1][i] = -1;
else parent[k+1][i] = parent[k][parent[k][i]];
}
}
}
int lca(int u, int v){
if(depth[u] > depth[v]) swap(u, v);
for(int k=0; k<LOG; k++){
if((depth[v]-depth[u]) >> k&1){
v = parent[k][v];
}
}
if(u==v) return u;
for(int k=LOG-1; k>=0; k--){
if(parent[k][u] != parent[k][v]){
u = parent[k][u];
v = parent[k][v];
}
}
return parent[0][u];
}
int main(){
in(n); in(m);
q = 0;
int c = 0;
for(int i=1; i<=n; i++) mk[i] = 1;
for(int i=0; i<m; i++){
in(t[i]); in(x[i]);
if(t[i]!=2) in(y[i]);
else y[i]=++c;
if(t[i]==1){
V[y[i]].pb(x[i]);
mk[x[i]] = 0;
}
else if(t[i]==3){
Q[y[i]].pb(Query(++q, x[i]));
}
}
for(int i=1; i<=n; i++){
f[i]=i;
}
init();
for(int i=0; i<m; i++){
if(t[i]==1){
f[x[i]] = y[i];
}
else if(t[i]==2){
int p = find(x[i]);
for(int j=0; j<Q[y[i]].size(); j++){
Query &qu = Q[y[i]][j];
int k = find(qu.x);
if(k!=p || lca(qu.x, x[i])!=qu.x){
ans[qu.id] = 0;
}
else{
ans[qu.id] = 1;
}
}
}
}
for(int i=1; i<=q; i++) puts(ans[i]?"YES":"NO");
return 0;
}