POI 2015 题解
在宋爷的教育下……我来自我教育一下,做一下近年的POI题……
论弱逼的自我修养
4385: [POI2015]Wilcze doły
Description
给定一个长度为n的序列,你有一次机会选中一段连续的长度不超过d的区间,将里面所有数字全部修改为0。
请找到最长的一段连续区间,使得该区间内所有数字之和不超过p。
Solution
显然,选择
d
个一定比不足
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 2000000+5;
using namespace std;
typedef long long LL;
LL q[N];
int l,r;
LL sum[N];
LL f[N],a[N];
inline LL read()
{
LL x = 0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x*f;
}
int main()
{
// freopen("ks.in","r",stdin);
// freopen("ks.out","w",stdout);
int n = read();
LL p = read();
int d = read();
for(int i=1;i<=n;++i) a[i] = read(), sum[i] = a[i] + sum[i-1];
for(int i=1;i<=n;++i) f[i] = sum[i+d-1] - sum[i-1];
int ans = d;
l = 1;
r = 1;
q[r] = 1;
LL sum = f[1];
for(int i=1,j=1;j<=n-d+1;)
{
while(sum - f[q[l]] <= p && j <= n-d+1){
j ++;
while(l <= r && f[q[r]] <= f[j] )--r;
q[++r] = j;
sum += a[j+d-1];
}
// cout << i << " " << j << endl;
ans = max(ans,j-i+d-1);
while(sum - f[q[l]] > p && i<=j){
while(l<=r && q[l]<=i) ++l;
sum -= a[i++];
}
}
cout << ans << endl;
}
4378[POI2015]Logistyka
维护一个长度为n的序列,一开始都是0,支持以下两种操作:
1.U k a 将序列中第k个数修改为a。
2.Z c s 在这个序列上,每次选出c个正数,并将它们都减去1,询问能否进行s次操作。
每次询问独立,即每次询问不会对序列进行修改。
Solution
我们先考虑,加入如果一段区间大于等于
s
的数字的个数
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#define lowbit(x) ((x)&(-x))
using namespace std;
const int N = 1000000;
typedef long long LL;
int T1[N+5];
LL T2[N+5];
struct ask
{
int chose;
int x,y;
}q[N+5];
void updata(int x,int num)
{
while(x <= N )
{
T1[x] += (num >= 0) ? 1 : -1;
T2[x] += (LL)num;
x += lowbit(x);
}
}
int ask1(int x)
{
int ans = 0;
while(x)
{
ans += T1[x];
x -= lowbit(x);
}
return ans;
}
LL ask2(int x)
{
LL ans = 0;
while(x)
{
ans += T2[x];
x -= lowbit(x);
}
return ans;
}
inline int read()
{
int x=0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x*f;
}
int a[N+5];
int V[N<<1];
int tt;
int find(int x)
{
int l = 1;
int r = tt;
while(l<=r)
{
int mid = (l+r)>>1;
if(V[mid] == x) return mid;
if(V[mid] < x) l = mid + 1;
else r = mid - 1;
}
return 0;
}
int main()
{
int n = read(), m = read();
char str[10];
for(int i=1;i<=m;++i)
{
scanf("%s",str);
q[i].chose = (str[0] == 'U');
q[i].x = read(),q[i].y = read();
V[++tt] = q[i].y;
}
V[++tt] = 0;
for(int i=1;i<=n;++i)
updata(1,0);
sort(V+1,V+tt+1);
for(int i=1;i<=m;++i)
{
if(q[i].chose == 1)
{
int tt = find(a[q[i].x]);
updata(tt,-a[q[i].x]);
a[q[i].x] = q[i].y;
tt = find(a[q[i].x]) ;
updata(tt,a[q[i].x]);
}
else
{
int tt = find(q[i].y);
int tt1 = ask1(N) - ask1(tt-1);
if(tt1 >= q[i].x){puts("TAK");continue;}
LL tt2 = ask2(tt-1);
if(tt2 >= ((LL)q[i].x - tt1)*q[i].y)
puts("TAK");
else puts("NIE");
}
}
}
3749: [POI2015]Łasuchy
圆桌上摆放着n份食物,围成一圈,第i份食物所含热量为c[i]。
相邻两份食物之间坐着一个人,共有n个人。每个人有两种选择,吃自己左边或者右边的食物。如果两个人选择了同一份食物,这两个人会平分这份食物,每人获得一半的热量。
假如某个人改变自己的选择后(其他n-1个人的选择不变),可以使自己获得比原先更多的热量,那么这个人会不满意。
请你给每个人指定应该吃哪一份食物,使得所有人都能够满意。
Solution
我们可以分析,对于一个人情况其实非常少,我们不妨尝试第一个人的每一种情况,之后每一个人的合法情况就都能知道了,然后就是模拟了
这题作为嘴巴选手AC还是非常容易的,但是真写起来这是…………
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 1000000+5;
using namespace std;
int n;
int a[N];
int path[N][5],ans[N];
inline int read()
{
int x=0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x*f;
}
bool work(int x)
{
memset(path,0,sizeof path);
path[1][x] = 1;
for(int i=2;i<=n;++i)
{
if(path[i-1][1] && (a[i-1] <= (a[i] << 1))) path[i][1] = 1;
if(path[i-1][4] && (a[i-1] <= a[i])) path[i][1] = 4;
if(path[i-1][2] && (a[i-1]<<1) >= a[i]) path[i][2] = 2;
if(path[i-1][3] && (a[i-1] >= a[i])) path[i][2] = 3;
if(path[i-1][1] && (a[i-1] <= a[i])) path[i][3] = 1;
if(path[i-1][4] && (a[i-1]<<1) <= a[i]) path[i][3] = 4;
if(path[i-1][2] && (a[i-1]>=a[i])) path[i][4] = 2;
if(path[i-1][3]&&(a[i-1]>=a[i]*2))path[i][4]=3;
}
if(path[n][x] == 0) return 0;
for(int i=n;i>=1;--i)
{
if(x == 1) ans[i-1] = (i-1) %(n-1) + 1;
if(x == 2) ans[i] = (i-1) %(n-1) + 1;
if(x == 3) ans[i-1] = ans[i] = (i-1)%(n-1) + 1;
x = path[i][x];
}
for(int i=1;i<n-1;++i) printf("%d ",ans[i]);
return printf("%d\n",ans[n-1]), 1;
}
int main()
{
n = read();
for(int i=1;i<=n;++i) a[i] = read();
a[++n] = a[1];
for(int i=1;i<=4;++i) if(work(i)) return 0;
puts("NIE");
return 0;
}
4383: [POI2015]Pustynia
Description
给定一个长度为n的正整数序列a,每个数都在1到10^9范围内,告诉你其中s个数,并给出m条信息,每条信息包含三个数l,r,k以及接下来k个正整数,表示a[l],a[l+1],…,a[r-1],a[r]里这k个数中的任意一个都比任意一个剩下的r-l+1-k个数大(严格大于,即没有等号)。
请任意构造出一组满足条件的方案,或者判断无解。
Solution
首先,如果数字的个数比较少,我们可以这样构建
1.如果存在一个数字
A
比另一个数字
2.对图进行拓扑排序,把起点的值设为 inf ,不断限制中间的点的最大值
3.如果有正环或者有点的范围不在规定的范围之内就 GG 了
然而这题的点数太多了,耿直的建边是 n2 级别的,所以我们需要一些技♂巧
我们可以学习 Vfleaking 在 A+BProblem 的经验,利用线段树来搞对于每一次的建边,我们可以这样
这是一颗正常的线段树
![](~/getGraph (1).jpg)
我们 把它改造,由父亲指向儿子,边权是0,表示没有区间的限制关系
.jpg)
这时,假设有一号位置的点限制了后面三个点,我们可以这样连边
![](~/getGraph (3).jpg)
然后,就和正常的一样了,拓扑排序判一个正环啥的就行了,由于边最多有 ∑k∗log(n) 个,复杂度近似就是 O(k∗log(n))
#include <queue>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 2000000;
const int M = 2000000;
const int inf = 1e9;
using namespace std;
int hash[N];
int tr[M];
int head[N];
char getc()
{
static const int LEN = 1<<15;
static char buf[LEN],*S=buf,*T=buf;
if(S == T)
{
T = (S=buf)+fread(buf,1,LEN,stdin);
if(S == T)return EOF;
}
return *S++;
}
int read()
{
static char ch;
static int D;
while(!isdigit(ch=getc()));
for(D=ch-'0'; isdigit(ch=getc());)
D=(D<<3)+(D<<1)+(ch-'0');
return D;
}
struct graph
{
int next,to,val;
graph () {}
graph (int _next,int _to,int _val)
:next(_next),to(_to),val(_val){}
}edge[M];
int in[M],MIN[M];
inline void add(int x,int y,int z)
{
static int cnt = 0;
edge[++cnt] = graph(head[x],y,z);
in[y] ++;
head[x] = cnt;
}
int cnt;
void build(int k,int l,int r)
{
tr[k] = ++cnt;
if(l==r){hash[l] = cnt;return;}
int mid = (l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
add(tr[k],tr[k<<1],0);
add(tr[k],tr[k<<1|1],0);
}
void ask(int k,int l,int r,int x,int y)
{
if(l==x && r==y){add(cnt,tr[k],1);return;}
int mid = (l+r)>>1;
if(y <= mid)ask(k<<1,l,mid,x,y);
else if(x > mid)ask(k<<1|1,mid+1,r,x,y);
else ask(k<<1,l,mid,x,mid),ask(k<<1|1,mid+1,r,mid+1,y);
}
queue <int> q;
int a[N];
int main()
{
register int i,j;
int n = read(),s = read(), m = read();
build(1,1,n);
for(i=1;i<=s;++i)
{
int tmp1 = read(),tmp2 = read();
a[tmp1] = tmp2;
MIN[hash[tmp1]] = tmp2;
}
for(i=1;i<=m;++i)
{
++cnt;
int l = read(),r = read(),k = read();
int lastL = l - 1,lastR = r + 1;
int pos;
for(j=1;j<=k;++j)
{
pos = read();
add(hash[pos],cnt,0);
if(pos - lastL > 1)
ask(1,1,n,lastL+1,pos-1);
lastL = pos;
}
if(lastR - lastL > 1)
ask(1,1,n,lastL + 1, lastR - 1);
}
for(i=1;i<=cnt;++i)
{
if(!MIN[i]) MIN[i] = inf;
if(!in[i]) q.push(i);
}
while(!q.empty())
{
int tt = q.front();q.pop();
for(i=head[tt];i;i=edge[i].next)
{
MIN[edge[i].to] = min(MIN[edge[i].to],MIN[tt] - edge[i].val);
if(!--in[edge[i].to])
q.push(edge[i].to);
}
}
bool flag = 0;
for(i=1;i<=cnt;++i)
if(in[i])
flag = 1;
for(i=1;i<=n;++i)
if(MIN[hash[i]] < 1)
flag = 1;
for(i=1;i<=n;++i)
if(a[i] > MIN[hash[i]])
flag = 1;
if(flag)
puts("NIE");
else
{
puts("TAK");
for(i=1;i<n;++i)
printf("%d ",MIN[hash[i]]);
cout << MIN[hash[n]] << endl;
}
}
4382: [POI2015]Podział naszyjnika
Description
长度为n的一串项链,每颗珠子是k种颜色之一。 第i颗与第i-1,i+1颗珠子相邻,第n颗与第1颗也相邻。
切两刀,把项链断成两条链。要求每种颜色的珠子只能出现在其中一条链中。
求方案数量(保证至少存在一种),以及切成的两段长度之差绝对值的最小值。
Solution
首先,考虑,如果只有两种字符,我们只需要在遇到他的时候+1,再次遇到他的时候-1,然后前缀和相等的地方都是能取得。
现在变多了,我们可以这样,还是维护一个 Pre 表示这个东西上一次出现的位置,每次,如果他有一个 Pre ,就加上一个数,然后自己减去相同的数字,然后前缀和相等的东西就都能取了。
由于串非常多,所以我们不能只是单纯的+1-1,我们可以利用 Hash ,然后 map 判重就行了。
对于第一问,我们遍历这个串,讲与他颜色相同的所有位置扔到一个大队列里面,然后记录一下一共有 cnt 个,那么这 cnt 个钟任意去都是可行的,所以贡献是 cnt∗(cnt−1)/2
对于第二问,直接双指针扫一遍就行了。
复杂度都在 map 上………………
#include <map>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1100000+5;
const int seed = 2333333;
typedef unsigned long long ULL;
inline int read()
{
int x=0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x*f;
}
map <ULL,int>mp;
ULL col[N],val[N],cnt;
ULL sum[N];
int pre[N],next[N],q[N],a[N],n,k;
bool vis[N];
int calc(int x,int y)
{
int tt = y - x;
return abs(tt-(n-tt));
}
int main()
{
col[0] = 1;
n = read(), k = read();
for(int i=1;i<=n;++i)col[i] = col[i-1] * seed;
int tot = 0;
for(int i=1;i<=n;++i) a[i] = read();
for(int i=1;i<=n;++i)
{
if(pre[a[i]])
{
val[pre[a[i]]] += col[++tot];
val[i] -= col[tot];
}
pre[a[i]] = i;
}
for(int i=1;i<=n;++i) sum[i] = sum[i-1] + val[i] ;
for(int i=1;i<=n;++i)
{
if(mp[sum[i]])
next[mp[sum[i]]] = i;
mp[sum[i]] = i;
}
int ans = n;
int tail = 0;
for(int i=1;i<=n;++i)
if(!vis[i])
{
tail = 0;
for(int j=i;j;j=next[j])
vis[j] = 1,q[++tail] = j;
// puts("");
cnt += (ULL)(tail)*(tail-1)/2;
for(int i=1,j=1;i<=tail;i++)
{
while(j < i && calc(q[j],q[i]) > calc(q[j+1],q[i])) ++j;
ans = min(ans,calc(q[j],q[i]));
}
}
cout << cnt << " " << ans << endl;
}
4377: [POI2015]Kurs szybkiego czytania
Description
给定n,a,b,p,其中n,a互质。定义一个长度为n的01串c[0..n-1],其中c[i]==0当且仅当(ai+b) mod n < p。
给定一个长为m的小01串,求出小串在大串中出现了几次。
Solution
首先, gcd(a,n)=1 ,所以 [0,n−1] 的每个数字恰好出现一次。
我们可以设短串的首个与长串的匹配位置为
i1
x=a∗i1+b
如果短串的第
i
个数为0,那么
否则不在 [0,p−1] 中,然后我们就可以吧 [0,n−1] 不断限制
因为串长
,
,所以
然后区间排个序,求个补集就行了
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 1100000+5;
using namespace std;
int cnt = 0,n,m,A,b,p;
int ans = 0;
struct interval
{
int l,r;
interval () {}
interval (int _l,int _r):l(_l),r(_r){}
bool operator<(const interval &z)const
{
return l < z.l;
}
}a[N<<2];
void add(int l,int r){
if(l<=r)a[++cnt] = interval(l,r);
else a[++cnt] = interval(l,n-1),a[++cnt] = interval(0,r);
}
char s[N];
int main()
{
cin >> n >> A >> b >> p >> m ;
scanf("%s",s);
for(int i=0,now = 0;i<m;++i,(now+=A)%=n)
if(s[i] == '0')
add((p-now+n)%n,(n-1-now+n)%n);
else
add((n-now+n)%n,(p+n-now-1)%n);
for(int i=1,c=(b-A+n)%n;i<m;++i,c=(c-A+n)%n)
add(c,c);
sort(a+1,a+cnt+1);
int tmp = -1;
for(int i=1;i<=cnt;++i)
{
if(a[i].l > tmp) ans += a[i].l-tmp-1,tmp = a[i].r;
else tmp = max(tmp,a[i].r);
}
cout << ans + n - 1 - tmp;
}
4384: [POI2015]Trzy wieże
sb BZ ,卡我常数,毁我青春
但是题还是不错的题
Description
给定一个长度为n的仅包含’B’、’C’、’S’三种字符的字符串,请找到最长的一段连续子串,使得这一段要么只有一种字符,要么有多种字符,但是没有任意两种字符出现次数相同
Solution
这题算是 JOI 一道题的加强版的,但是强了很多……
JOI 是求相等的个数,我们维护 sumi,sumc,sums 两两的差就行了,每次对应在 map 上插入位置,然后每次更新答案就行了
这题我们可以也维护这三个东西,然后发现这其实是三维的东西,争取固定两维比较第三维
我们可以先把第一维排序,然后相同的第一维一起更新答案,再一起更新数据,处理了一维
第把第二维做成桶或者叫权值树状数组,维护差为那个的时候坐标的最大值,最小值
但是这样还不够,可能会出现第三维相同的情况,为了规避这种情况,我们再维护一下最大值的第三维是什么。
但是发现,如果冲突了也没法解决,于是我们在维护一个次小值就行了,显然这两个的第二维不同
写起来真是…………………………………………………………………………
#include <bits/stdc++.h>
using namespace std;
#define N 1100000
int n,lim,ans;
char s[N];
struct node
{
int a,b,c,pos;
node(){}
node(int a,int b,int c,int pos):a(a),b(b),c(c),pos(pos){}
friend bool operator < (const node &r1,const node &r2)
{return r1.a<r2.a;};
}p[N];
struct diw_tree
{
int mx[N<<1],sx[N<<1],cx[N<<1],mn[N<<1],sn[N<<1],cn[N<<1];
void init()
{
memset(mx,-0x3f,sizeof(mx));
memset(sx,-0x3f,sizeof(sx));
memset(mn,0x3f,sizeof(mn));
memset(sn,0x3f,sizeof(sn));
}
void insert(int x,int y,int v)
{
for(int i=x;i<=lim;i+=i&-i)
{
if(v>mx[i])
{
if(cx[i]!=y)sx[i]=mx[i];
mx[i]=v;cx[i]=y;
}
else if(y!=cx[i])
sx[i]=max(sx[i],v);
if(v<mn[i])
{
if(cn[i]!=y)sn[i]=mn[i];
mn[i]=v;cn[i]=y;
}
else if(y!=cn[i])
sn[i]=min(sn[i],v);
}
}
inline void query(int x,int y,int v)
{
for(int i=x;i;i-=i&-i)
{
if(cx[i]!=y)ans=max(ans,mx[i]-v);
else ans=max(ans,sx[i]-v);
if(cn[i]!=y)ans=max(ans,v-mn[i]);
else ans=max(ans,v-sn[i]);
}
}
}tr1,tr2;
int main()
{
scanf("%d",&n);lim=(n+1)<<1;
scanf("%s",s+1);
for(int i=1,j;i<=n;i++)
{
j=i;
while(s[j]==s[i]&&j<=n)j++;j--;
ans=max(ans,j-i+1);i=j;
}
for(int i=1,a=0,b=0,c=0;i<=n;i++)
{
if(s[i]=='B')a++;
if(s[i]=='C')b++;
if(s[i]=='S')c++;
p[i]=node(a-b,b-c,c-a,i);
}
tr1.init();tr2.init();
sort(p,p+1+n);
for(int i=0,j;i<=n;i++)
{
j=i;
while(p[j].a==p[i].a&&j<=n)j++;j--;
for(int k=i;k<=j;k++)
{
tr1.query(p[k].b-1+n+1,p[k].c,p[k].pos);
tr2.query(n-(p[k].b+1)+1,p[k].c,p[k].pos);
}
for(int k=i;k<=j;k++)
{
tr1.insert(p[k].b+n+1,p[k].c,p[k].pos);
tr2.insert(n-p[k].b+1,p[k].c,p[k].pos);
}
i=j;
}
printf("%d\n",ans);
return 0;
}
rank2 的 jkxing 只写了1k,1s秒过……,我问他写了啥
他告诉我,一些奇怪的特判……
Orz
4381: [POI2015]Odwiedziny
Description
给定一棵
n
个点的树,树上每条边的长度都为1,第i个点的权值为
Byteasar
想要走遍这整棵树,他会按照某个1到n的全排列b走n-1次,第i次他会从b[i]点走到
b[i+1]
点,并且这一次的步伐大小为
c[i]
。
对于一次行走,假设起点为x,终点为y,步伐为k,那么Byteasar会从x开始,每步往前走k步,如果最后不足k步就能到达y,那么他会一步走到y。
请帮助
Byteasar
统计出每一次行走时经过的所有点的权值和。
Solution
调代码的时候内心是崩溃的
好在开始写的时候写的不是很残
首先,如果直接树链剖分肯定是不行的[虽然这题确实能过
因为极限情况其实会被卡成
O(n2)?
不知道 没算过 有算出更靠谱的可以下面留言
然后我们可以预处理一部分,正常的算法应该是像倍增预处理两个东西
1.
pos[i][j]
表示
i
这个点向上走
2.
F[i][j]
表示每次跳
j
个路径上的权值的和,显然有
然后我们可以处理出 n√ 里面的信息,对于询问我们分类,一种是步长 <n√ <script type="math/tex" id="MathJax-Element-96"><\sqrt{n}</script>的,我们可以直接前缀和搞一下
另外一种直接在树剖上边走边统计就行了
这题真正的难点在于只能往上走,对于另外一边的子树,就会发生跨过 lca 的情况,然后你就需要特判了,是不是恰好跳到 lca 上啊,剩了多少步啊,现在还得跳多少步啊,能不能整除啊
写出来就是几行,几个字节GG就够你调一阵的了
另外阈值其实不一定非得是 n√ ,由于数据是随机数据,我们只需要调到 logn 就行了,速度奇佳,现在是 BZ 的 rank3
PS:经过多组测试,发现速度-阈值函数是一个单峰函数,在15左右取到速率的最大值
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 50000+5;
const int M = N << 1;
const int lmt = 15;
using namespace std;
int head[N];
struct graph
{
int next,to;
graph () {}
graph (int _next,int _to)
:next(_next),to(_to){}
}edge[M];
inline void add(int x,int y)
{
static int cnt = 0;
edge[++cnt] = graph(head[x],y);
head[x] = cnt;
edge[++cnt] = graph(head[y],x);
head[y] = cnt;
}
int fa[N];
int PP[N][lmt+1];
int sum[N][lmt+1];
int depth[N],size[N],top[N];
int pos[N];
int belong[N];
int a[N];
void DFS1(int x)
{
size[x] = 1;
for(int i=2;i<=lmt;++i)
if(PP[fa[x]][i-1])
PP[x][i] = PP[fa[x]][i-1];
for(int i=1;i<=lmt;++i)
sum[x][i] = sum[PP[x][i]][i] + a[x];
for(int i=head[x];i;i=edge[i].next)
if(edge[i].to!=fa[x])
{
fa[edge[i].to] = x;
depth[edge[i].to] = depth[x] + 1;
PP[edge[i].to][1] = x;
DFS1(edge[i].to);
size[x] += size[edge[i].to];
}
}
void DFS2(int x,int chain)
{
static int cnt = 0;
belong[pos[x] = ++cnt] = x;
top[x] = chain;int k = 0;
for(int i=head[x];i;i=edge[i].next)
if(edge[i].to!=fa[x] && size[k] < size[edge[i].to])
k = edge[i].to;
if(!k) return; DFS2(k,chain);
for(int i=head[x];i;i=edge[i].next)
if(edge[i].to!=fa[x] && edge[i].to != k)
DFS2(edge[i].to,edge[i].to);
}
int Lca(int x,int y)
{
while(top[x] != top[y])
{
if(depth[top[x]] < depth[top[y]])swap(x,y);
x = fa[top[x]];
}
return depth[x] < depth[y] ? x : y;
}
int up(int x,int y)
{
while(depth[x] - depth[top[x]] < y)
{
y -= depth[x] - depth[top[x]] + 1;
x = fa[top[x]] ;
}
// cout << belong[pos[x]-y] <<endl;
return belong[pos[x] - y];
}
int Get(int x,int y,int v)
{
if(v < lmt) return sum[x][v] - sum[y][v] + a[y];
int ans = 0 , remain = 0 , t;
while(top[x]!=top[y])
{
for(t = pos[x] - remain ; t >= pos[top[x]]; t -= v)
ans += a[belong[t]];
t += v;
remain = v - ( t - pos[top[x]] + 1 );
x = fa[top[x]];
}
for(t = pos[x] - remain;t>=pos[y]; t-= v)
ans += a[belong[t]];
return ans;
}
int calc(int x,int y,int v)
{
int z = Lca(x,y);
int ans = 0;
int t = depth[x] - depth[z] - ( depth[x] - depth[z] )%v;
t = up(x,t);
ans = Get(x,t,v);
if(y == z)
return t == y ? ans : a[y];
int t1 = v - (depth[t] - depth[z]);
int t2 = depth[y] - depth[z] - t1;
if(t2 < 0) return ans + a[y];
int t3 = up(y,t2%v);
int t4 = up(y,t2);
ans += Get(t3,t4,v);
return t2%v ? ans + a[y] : ans;
}
inline int read()
{
int x=0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x * f;
}
int b[N];
int main()
{
int n = read();
for(int i=1;i<=n;++i) a[i] = read();
for(int i=1;i<n;++i)
{
int x = read(),y = read();
add(x,y);
}
DFS1(1);
DFS2(1,1);
/* for(int i=1;i<=n;++i)
{
for(int j=1;j<=3;++j)
cout << sum[i][j] << " ";
puts("");
}*/
for(int i=1;i<=n;++i) b[i] = read();
for(int i=1;i<n;++i){
int x = read();
printf("%d\n",calc(b[i],b[i+1],x));
}
}
4380: [POI2015]Myjnie
Description
有n家洗车店从左往右排成一排,每家店都有一个正整数价格p[i]。
有m个人要来消费,第i个人会驶过第a[i]个开始一直到第b[i]个洗车店,且会选择这些店中最便宜的一个进行一次消费。但是如果这个最便宜的价格大于c[i],那么这个人就不洗车了。
请给每家店指定一个价格,使得所有人花的钱的总和最大。
Solution
首先,我们可以把他的价格离散,然后用
f(i,j,k)
表示从
i
到
然后我们发现可以区间 dp
为了方便转移,再维护一个后缀的最大值就行了
最后递归输出一下
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int N = 50+2;
const int M = 4000+2;
int n,m,cnt;
int l[M],r[M],c[M],a[M];
int pos[N][N][M],val[N][N][M];
LL f[N][N][M],sum[N][N][M];
int V[M];
void print(int l,int r,int v)
{
if(r < l) return;
int tt = val[l][r][v];
print(l,pos[l][r][v]-1,tt);
printf("%d ",V[tt]);
print(pos[l][r][v]+1,r,tt);
}
inline int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int stack[M];
int main()
{
cin >> n >> m;
register int k;
for(int i=1;i<=m;++i) l[i] = read(),r[i] = read(),V[i] = c[i] = read();
int top;
sort(V+1,V+m+1);
for(int i=1;i<=m;++i)
if(V[i]!=V[i-1])
V[++cnt] = V[i];
for(int i=0;i<n;++i)
for(int j=1;j+i<=n;++j)
{
int ri = j + i;
for(k=j;k<=ri;++k)
{
top = 0;
for(int t=1;t<=m;++t)
if(l[t] >= j && r[t] <= ri && l[t] <= k && r[t] >= k)
stack[++top] = c[t];
sort(stack+1,stack+top+1);
for(int t=1,now=1;t<=cnt;++t)
{
while(now <= top && stack[now] < V[t]) ++ now;
LL t1 = sum[j][k-1][t] + sum[k+1][ri][t] + (LL)V[t]*(top-now+1);
if(t1 >= f[j][ri][t])
{
f[j][ri][t] = t1;
pos[j][ri][t] = k;
}
}
}
for(k=cnt;k>=1;--k)
{
val[j][ri][k] = k;
if(f[j][ri][k] < sum[j][ri][k+1])
{
pos[j][ri][k] = pos[j][ri][k+1];
val[j][ri][k] = val[j][ri][k+1];
}
sum[j][ri][k] = max(sum[j][ri][k+1],f[j][ri][k]);
}
}
cout << sum[1][n][1] << endl;
print(1,n,1);
}
3747- [POI2015]Kinoman
Description
共有m部电影,编号为1~m,第i部电影的好看值为w[i]。
在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。
你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是
无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。
Solution
我们可以枚举左端点线段树处理右端点
考虑到对
next[i]
和
next[next[i]]
的影响就行了
具体可以参见HH的项链一题
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#define N 1000000+5
#define M 4000000+5
typedef long long LL;
using namespace std;
LL tr[M],lazy[M];
inline void updata(int k)
{
tr[k] = max(tr[k<<1],tr[k<<1|1]);
}
inline LL read()
{
LL x=0,f=1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
return x*f;
}
inline void down(int k,int l,int r)
{
if(l==r || !lazy[k])return;
LL tmp = lazy[k];lazy[k] = 0;
tr[k<<1] += tmp;tr[k<<1|1] += tmp;
lazy[k<<1] += tmp;lazy[k<<1|1]+=tmp;
}
inline void change(int k,int l,int r,int x,int y,LL c)
{
down(k,l,r);
if(l==x && r==y){lazy[k] += c;tr[k] += c;return;}
int mid = (l+r)>>1;
if(y<=mid)change(k<<1,l,mid,x,y,c);
else if(x>mid)change(k<<1|1,mid+1,r,x,y,c);
else change(k<<1,l,mid,x,mid,c),change(k<<1|1,mid+1,r,mid+1,y,c);
updata(k);
}
LL a[N],b[N];
LL next[N],last[N];
int main()
{
int n = read(), m =read();
for(int i=1;i<=n;++i)
a[i] = read();
for(int i=1;i<=m;++i)
b[i] = read();
for(int i=n;i>=1;--i)
next[i] = last[a[i]],last[a[i]] = i;
for(int i=1;i<=m;++i)
if(last[i])
{
if(!next[last[i]])
change(1,1,n,last[i],n,b[i]);
else
change(1,1,n,last[i],next[last[i]]-1,b[i]);
}
LL ans = 0;
for(int i=1;i<=n;++i)
{
ans = max(ans,tr[1]);
int t = next[i];
if(t)
{
change(1,1,n,i,t-1,-b[a[i]]);
if(next[t])
change(1,1,n,t,next[t]-1,b[a[i]]);
else
change(1,1,n,t,n,b[a[i]]);
}
else
change(1,1,n,i,n,-b[a[i]]);
}
cout<<ans<<endl;
}
3750: [POI2015]Pieczęć
Description
一张n*m的方格纸,有些格子需要印成黑色,剩下的格子需要保留白色。
你有一个a*b的印章,有些格子是凸起(会沾上墨水)的。你需要判断能否用这个印章印出纸上的图案。印的过程中需要满足以下要求:
(1)印章不可以旋转。
(2)不能把墨水印到纸外面。
(3)纸上的同一个格子不可以印多次。
Solution
mdzz
这是POI2015最水没有之一,显然现在途中最左上的点必然对应着一个印章的最左上
然后 思博模拟
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 1000+5;
using namespace std;
struct data
{
int x,y;
data () {}
data (int _x,int _y)
:x(_x),y(_y){}
}p[N*N];
int cnt;
char s1[N][N],s2[N][N];
int main()
{
int T;
cin >> T;
int n,m,a,b;
while(T--)
{
cin >> n >> m >> a >> b;
for(int i=1;i<=n;++i)scanf("%s",s1[i]+1);
cnt = 0;
for(int i=1;i<=a;++i)
{
scanf("%s",s2[i]+1);
for(int j=1;j<=b;++j)
if(s2[i][j] == 'x')
p[++cnt] = data(i,j);
}
bool flag = false;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(s1[i][j] == 'x')
{
for(int k=1;k<=cnt;++k)
{
data t1 = data(i+p[k].x-p[1].x,j+p[k].y-p[1].y);
if(t1.x > n || t1.y > m )
{
flag = 1;
break;
}
if(s1[t1.x][t1.y] != 'x')
{
flag = 1;
break;
}
s1[t1.x][t1.y] = '.';
}
}
if(flag) break;
}
if(flag) break;
}
if(flag)
puts("NIE");
else puts("TAK");
}
}