您的位置:首页 > 其它

【2017/9/19】【校内胡策题】【noip模拟】【总结】

2017-09-19 14:53 375 查看


似乎永远用不完的图报

第一题

此题很有复习价值,本题中有几个点重点:

【1】 对于可行问题要往贪心方向思考

【2】 在想贪心方案是需要严格证明,宁可画上一定时间(但要控制再半小时以内)去证明正确性 , 找反例

【3】 在想贪心情况时 , 要落到实处 ,若觉得一个策略不对则需要通过题目特性落到实处看其是否正确;找反例时先感性想想反例出现情况,在去构造对应数据看是否能否定当前结论

在解决此题时 , 由于放入体积与增加体积,而且题目问题是是否可解,可使人想到贪心;

而贪心策略中,由于要是其放得下,所以可以想到先放有贡献(增加体积)的情况 , 然后猜测是贪心顺序,以a排序?b排序?还是b-a?此时发现它最后加完的体积是一个定值 , 而其体积也在不断增加,那么应该是a越大的放到越后面能跟保险的放下,然而是否会有反例,根据其特性:v会应为b - a > 0 而不断增大,显而易见策略正确;

而此题头疼的是b - a < 0 时的策略 : 还是按照上面所述的思路 , a?,b?,b-a?,按照从大到小?从小到大?貌似从正面不好想,但有个性质时确定的:

最后的体积为定值

那么从次特性思考,倒着来想:

最后的状态是:

V + b1 + b2 + b3 + … + bn >= a1 + a2 + a3 + … + an

那么最后一次选择便是选择先将左式减去对于的b , 在不等式成立的情况下在减去对应得a ,由于后面加上的是b - a < 0 , 那么每次减去一组对应的a,b时左式减右式的值会越来越大 , 所以只要保证每次删的b最小,时左式减小的尽量少,那么等式则月容易成立,所以嘛。。。反过来想 , 不就是。。。以b为标准从大到小排序。。。

那么全程的贪心策略则出来了:

对于 a - b > 0 的情况 以a为标准从小到大排序

对于 a - b < 0 的情况 以b为标准从大到小排序

代码如下:

#include<cctype>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 100001
using namespace std;
long long T , n ;
long long v;
long long a , b;
long long qu1_tot , qu2_tot;
struct Queue{
long long a , b_a , b;
Queue(){a = b_a = 0;}
}qu1[M] , qu2[M];
bool cmp1(const Queue &x , const Queue &y){return x.a == y.a ? x.b_a > y.b_a : x.a < y.a;}

bool cmp2(const Queue &x , const Queue &y){return x.b == y.b ? x.a > y.a : x.b > y.b;}
long long read(){
long long an = 0;
static char ch = '*';
while(!isdigit(ch))ch = getchar();
while( isdigit(ch))an = an * 10 + ch - '0' , ch = getchar();
return an;
}
int main(){
T = read();
while(T--){
n = read() , v = read();
bool bre = true;
qu1_tot = 0 , qu2_tot = 0;
memset(qu1 , 0 , sizeof qu1);
memset(qu2 , 0 , sizeof qu2);
for(int i = 1 ; i <= n ; ++i){
a = read() , b = read();
long long b_a = b - a;
if(b - a >= 0)qu1[++qu1_tot].a = a , qu1[qu1_tot].b_a = b_a , qu1[qu1_tot].b = b;
else qu2[++qu2_tot].a = a , qu2[qu2_tot].b_a = b_a , qu2[qu2_tot].b = b;
}
sort(qu1 + 1 , qu1 + qu1_tot + 1 , cmp1);
sort(qu2 + 1 , qu2 + qu2_tot + 1 , cmp2);
for(int i = 1 ; i <= qu1_tot && bre ; ++i){
if(qu1[i].a > v)bre = false;
else v += qu1[i].b_a;
}
if(bre == false){
printf("No\n");
continue;
}
for(int i = 1 ; i <= qu2_tot && bre ; ++i){
if(qu2[i].a > v)bre = false;
else v += qu2[i].b_a;
}if(bre == false){
printf("No\n");
continue;
}
printf("Yes\n");
}
}


第二题

这道题由于时500000的上线而且为区间,很容易想到二分加线段树维护,代码如下(由于卡产只能得65分)(复杂度O(n * (log^3))):

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 1500001
using namespace std;
int n;
long long gcd(long long a , long long b){return b > 0 ? gcd(b , a%b) : a;}
long long v_num[M] , v_gcd[M];
int l[M] , r[M];
long long num[M];
long long tot , ans , set[M];
struct val{
long long gcd , num;
val(){gcd = 1 , num = (1 << 31) - 1;}
val(long long x , long long y){gcd = x , num = y;}
};
long long read(){
long long an = 0;
static char ch = '*';
while(!isdigit(ch))ch = getchar();
while( isdigit(ch))an = an * 10 + ch - '0' , ch = getchar();
return an;
}
void build(int l_ ,  int r_ , int v){
if(l_ == r_){
v_num[v] = v_gcd[v] = num[l_];
return;
}
l[v] = l_ , r[v] = r_;
int m_ = (l_ + r_) >> 1;
build(l_ , m_ , v << 1);
build(m_ + 1 , r_ , v << 1 | 1);
v_gcd[v] = gcd(v_gcd[v << 1] , v_gcd[v << 1 | 1]);
v_num[v] = min(v_num[v << 1] , v_num[v << 1 | 1]);
//  printf("[%d %d %I64d %I64d]\n",l_,r_,v_num[v] , v_gcd[v]);
}
val query(int l_ , int r_ , int qu_l , int qu_r , int v){
if(l_ >= qu_l && r_ <= qu_r)return val(v_gcd[v] , v_num[v]);
int m_ = (l_ + r_) >> 1;
val a , b , ret;
if(qu_l <= m_ && qu_r > m_){
a = query(l_ , m_ , qu_l , qu_r , v << 1);
b = query(m_ + 1 , r_ , qu_l , qu_r , v << 1 | 1);
ret.gcd = gcd(a.gcd , b.gcd);
ret.num = min(a.num , b.num);
}
else if(qu_l <= m_)ret = query(l_ , m_ , qu_l , qu_r , v << 1);
else if(qu_r >  m_)ret = query(m_ + 1 , r_ , qu_l , qu_r , v << 1 | 1);
return ret;
}
bool check(int lon){
for(int i = 1 ; i <=n - lon + 1 ; ++i){
val x = query(1 , n , i , i + lon - 1 , 1);
if(x.gcd == x.num)return true;
}
return false;
}
void find(int lon){
ans = lon * 1LL;
for(int i = 1 ; i <=n - lon + 1 ; ++i){
val x = query(1 , n , i , i + lon - 1 , 1);
if(x.gcd == x.num)  set[++tot] = i;
}

}
int main(){
freopen("point.in","r",stdin);
scanf("%d",&n);
for(register int i = 1 ; i <= n ; ++i){
num[i] = read();
//scanf("%I64d",&num[i]);
if(num[i] == 1){
printf("1 %d\n1",n - 1);
return 0;
}
}
build(1 , n , 1);
int L = 1 , R = n;
while(L + 1 < R){
int M_ = (L + R) >> 1;
check(M_) ? L = M_ : R = M_;
}
if(check(R))find(R);
else find(L);
printf("%I64d %I64d\n" , tot , ans - 1);
for(int i = 1 ; i <= tot ; ++i)printf("%I64d ",set[i]);
}


然而其实有更好的方法,由于时连续的区间,所以更具其【连续性】,那么每次维护一个以当前为ak而且是该区间最后一个数的满足条件的区间的长度和以当前为ak而且是该区间第一个数的满足条件的区间长;再来O(n)枚举即可

但是有一点需要注意,就是判重 , (一个合理区间里有多个相同的数)

例子:

4

1 1 1 1

代码如下:(复杂度O(n))

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 1000001
using namespace std;
int n , ans = 0  , top , vis[M];
long long qu[M];
int pre[M] , nex[M];
long long  num[M];
int main(){
scanf("%d",&n);
for(int i = 1 ; i <= n ; ++i)scanf("%I64d",&num[i]);
for(int i = 1 ; i <= n ; ++i){
int set = i ; pre[i] = i;
while(set && num[set] % num[i] == 0)set = pre[set] == set ? set - 1 : pre[set];
pre[i] = set + 1;
}
for(int i = n ; i >= 1 ; --i){
int set = i ; nex[i] = i;
while(set < n + 1 && num[set] % num[i] == 0)set = nex[set] == set ? set + 1 : nex[set];
nex[i] = set - 1;
}
for(int i = 1 ; i <= n ; i++){
if(ans < nex[i] - pre[i]){
ans = nex[i] - pre[i];
memset(vis , 0 , sizeof vis);
top = 0;
}
if(ans == nex[i] - pre[i])
if(!vis[pre[i]]){
vis[pre[i]] = 1;
qu[++top] = pre[i];
}
}
printf("%d %d\n",top , ans);
for(int i = 1 ; i <= top ; i++)printf("%I64d ",qu[i]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: