今天真是颓废的一天,cf写了两个思维之后做了个线段树和深搜就做不动了。
但是无论如何,也要把这一天的成果记录一下
D. Bash and a Tough Math Puzzle
虽然是1900,但是思维含量稍低,只要能稍微熟练的用一点线段树就ok
题意:
输入一个长度为n的数组,执行m次操作
操作1:询问区间l到r内是否可以修改至多一个数让整个区间的gcd为x
操作2:修改数组的第i个位置为k
思路:由于数据范围5e5,暴力显然GG,考虑区间操作采用线段树
至于gcd的操作,只要这一段区间内有多于一个取模于x不为0的数那么就不满足条件
证明:如果所有的数都取模x为0,或者只有一个取模于x不为0,那么把一个数改成x就能让gcd为x。
ok,理论存在实践开始
#include <bits/stdc++.h>
using namespace std;
#define N 500000+100
#define ls p<<1
#define rs p<<1|1
#define mid (l+r)/2
int a[N];
int tree[N<<2];
void up(int p)
{
tree[p]=__gcd(tree[ls],tree[rs]);
}
void bulid(int l,int r,int p)
{
if(l==r)
{
tree[p]=a[l];
return ;
}
bulid(l,mid,ls);
bulid(mid+1,r,rs);
up(p);
}
void fix(int l,int r,int pos,int k,int p)
{
if(l==r)
{
tree[p]=k;
return ;
}
if(pos<=mid)
{
fix(l,mid,pos,k,ls);
}
if(pos>mid)
{
fix(mid+1,r,pos,k,rs);
}
up(p);
}
int query(int l,int r,int nl,int nr,int p,int k)
{
int res=0;
if(l==r)
{
if(tree[p]%k)
return 1;
else
return 0;
}
if(nl<=mid)
res+=query(l,mid,nl,nr,ls,k);
if(nr>mid)
res+=query(mid+1,r,nl,nr,rs,k);
return res;
}
int main()
{
int n,q;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
bulid(1,n,1);
cin>>q;
for(int i=1;i<=q;i++)
{
int c,l,r,k;
cin>>c;
if(c==1)
{
cin>>l>>r>>k;
if(query(1,n,l,r,1,k)>1)
{
cout<<"NO"<<endl;
}
else
{
cout<<"YES"<<endl;
}
}
else
{
cin>>l>>k;
fix(1,n,l,k,1);
}
}
return 0;
}
然后你就会惊讶的发现:卧槽?TLE了
那我看看那里还能优化
0.头文件优化
1.输入输出优化
2.gcd函数优化
3.线段树优化
由于线段树的修改和建立操作已经无法优化了,所以考虑优化查询操作
我们发现查询操作是这样写的
int query(int l,int r,int nl,int nr,int p,int k)
{
int res=0;
if(l==r)
{
if(tree[p]%k)
return 1;
else
return 0;
}
if(nl<=mid)
res+=query(l,mid,nl,nr,ls,k);
if(nr>mid)
res+=query(mid+1,r,nl,nr,rs,k);
return res;
}
实际上我们可以直接在下面的if中判断是否递归
优化后第二版
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
#define N 500000+100
#define ls p<<1
#define rs p<<1|1
#define int long long
#define mid ((l+r)>>1)
int a[N];
int tree[N<<2];
void up(int p)
{
tree[p]=__gcd(tree[ls],tree[rs]);
}
void bulid(int l,int r,int p)
{
if(l==r)
{
tree[p]=a[l];
return ;
}
bulid(l,mid,ls);
bulid(mid+1,r,rs);
up(p);
}
void fix(int l,int r,int pos,int k,int p)
{
if(l==r)
{
tree[p]=k;
return ;
}
if(pos<=mid)
{
fix(l,mid,pos,k,ls);
}
if(pos>mid)
{
fix(mid+1,r,pos,k,rs);
}
up(p);
}
int query(int l,int r,int nl,int nr,int p,int k)
{
int res=0;
if(l==r)
{
return 1;
}
if(nl<=mid&&tree[ls]%k)
res+=query(l,mid,nl,nr,ls,k);
if(nr>mid&&tree[rs]%k)
res+=query(mid+1,r,nl,nr,rs,k);
return res;
}
signed main()
{
int n,q;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
bulid(1,n,1);
cin>>q;
for(int i=1;i<=q;i++)
{
int c,l,r,k;
scanf("%lld",&c);
if(c==1)
{
scanf("%lld%lld%lld",&l,&r,&k);
if(query(1,n,l,r,1,k)>1)
{
puts("NO");
}
else
{
puts("YES");
}
}
else
{
scanf("%lld%lld",&l,&k);
fix(1,n,l,k,1);
}
}
return 0;
}
你会发现:WTF!TLE on TEST 68!?
我们发现第68组样例还是和蔼可亲的500000数据拉满,不过这次人家为了测试你是否优化查询操作到尾,特地搞了400000个查询操作(我日你先人)
我们再来看优化,发现query中记录的res值实际上到了大于1以后就不用记录了,所以我们可以加一个可行性剪枝。
终于AC代码诞生了
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
#define N 500000+100
#define ls p<<1
#define rs p<<1|1
#define int long long
#define mid ((l+r)>>1)
int a[N];
int cnt;
int tree[N<<2];
void up(int p)
{
tree[p]=__gcd(tree[ls],tree[rs]);
}
void bulid(int l,int r,int p)
{
if(l==r)
{
tree[p]=a[l];
return ;
}
bulid(l,mid,ls);
bulid(mid+1,r,rs);
up(p);
}
void fix(int l,int r,int pos,int k,int p)
{
if(l==r)
{
tree[p]=k;
return ;
}
if(pos<=mid)
{
fix(l,mid,pos,k,ls);
}
if(pos>mid)
{
fix(mid+1,r,pos,k,rs);
}
up(p);
}
void query(int l,int r,int nl,int nr,int p,int k)
{
if(cnt>1)
return;
if(l==r)
{
cnt++;
return ;
}
if(nl<=mid&&tree[ls]%k)
query(l,mid,nl,nr,ls,k);
if(nr>mid&&tree[rs]%k)
query(mid+1,r,nl,nr,rs,k);
}
signed main()
{
int n,q;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
bulid(1,n,1);
cin>>q;
for(int i=1;i<=q;i++)
{
int c,l,r,k;
scanf("%lld",&c);
if(c==1)
{
scanf("%lld%lld%lld",&l,&r,&k);
cnt=0;
query(1,n,l,r,1,k);
if(cnt>1)
{
puts("NO");
}
else
{
puts("YES");
}
}
else
{
scanf("%lld%lld",&l,&k);
fix(1,n,l,k,1);
}
}
return 0;
}
记录一下我和这题的相爱相杀
反思:
1.gcd的思维含量有待补充
2.线段树点操作有待精进
3.线段树的优化不熟悉
好了,既然提到了剪枝,我们不得不说今天做的这个题了
根据题目,我们发现这题是DP题,很像背包问题,但是今天我啊,就是想深搜过他!
题意:n个积木摞两塔,要求两个塔一样高,问问到底能多高。
思路:我们分三个方向DFS
1.第n个积木不用
2.第n个积木给第一个塔
3.第n个积木给第二个塔
我们发现,这复杂度,爆炸了。
我们加一点点剪枝
可行性剪枝
if(now==n+1)
return;
if(h1+sum[n]-sum[now-1]<h2)
return;
if(h2+sum[n]-sum[now-1]<h1)
return;
最优解剪枝
if(h1+sum[n]-sum[now-1]<=maxx)
return;
if(h2+sum[n]-sum[now-1]<=maxx)
return;
if(h1+sum[n]-sum[now-1]==h2)
{
maxx=max(maxx,h2);
return;
}
if(h2+sum[n]-sum[now-1]==h1)
{
maxx=max(maxx,h1);
return;
}
剪枝完了发现:最后两个n给到50的点还是T(我草这年轻人)
最后上DFS优化的最后一件法宝:记忆化
#include <bits/stdc++.h>
#define ll long long
#define N 100010
#define M 60
using namespace std;
int n, a[M], ans;
int sum[M];
map<pair<int, pair<int, int> >, int>ma;
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
bool cmp(int a, int b) {
return a > b;
}
int dfs(int now, int h1, int h2) {
if (now == n + 1) {
if (h1 == h2) {
ans = max(ans, h1);
return h1;
}
return -1;
}
if (ma[make_pair(now, make_pair(h1, h1))])
return ma[make_pair(now, make_pair(h1, h1))];
if (h1 + h2 + sum[n] - sum[now - 1] <= ans * 2) return -1;
if (h1 + sum[n] - sum[now - 1] <= ans) return -1;
if (h2 + sum[n] - sum[now - 1] <= ans) return -1;
if (h1 + sum[n] - sum[now - 1] < h2) return -1;
if (h2 + sum[n] - sum[now - 1] < h1) return -1;
int s = 0;
s = max(s, dfs(now + 1, h1 + a[now], h2));
s = max(s, dfs(now + 1, h1, h2 + a[now]));
s = max(s, dfs(now + 1, h1, h2));
ma[make_pair(now, make_pair(h1, h1))] = s;
return s;
}
int main() {
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
dfs(1, 0, 0);
if (ans == 0) puts("-1");
else cout << ans;
}