10.29 模拟赛
t1
题意
有 \(q\) 个人在一个神秘的迷宫中寻宝,每个人都从某一个位置开始,寻找属于自己的宝藏。
这个迷宫可以描述为一个 \(n \times m\) 的网格图,从上到下第 \(x\) 行,从左到右第 \(y\) 列的位置用 \((x, y)\) 表示。
每个人可以在迷宫中移动若干次,每次可以向上下左右相邻的格子进行移动,但是无法移动到含有障碍物的格子。
除此以外,迷宫中还有 \(k\) 个传送门,每当一个人处在传送门入口所在的位置,就可以选择直接传送到出口所在的位置,传送门是单向的。
得知了迷宫的具体构造之后,现在小X想知道,哪些人能够成功找到自己的宝藏。
对于 \(100\%\) 的数据,满足 \(n \times m \leq 50000\),\(k \leq 100\),\(q \leq 100000\)。
题解
先不考虑传送门做一次 \(dfs\) 判断连通性。然后发现传送门比较少,用传送门把联通块连起来。对于每一个询问跑一次 \(dfs\),可以做到 \(O(nm+qk)\)。
但是我考场上做法比较猎奇,我没看到传送门这么少,我考虑对联通块做 \(tarjan\),去掉环之后维护这个 \(dag\) 的连通性,用 \(bitset\) 维护,时间复杂度 \(O(nm+\frac{n^2m^2}{w})\) 。
const int N=5e4+10;
int n,m,qq,k,T,a[N],fa[N],vis[N],ans[N];
int dfn[N],low[N],tot,col,scc[N],d[N];
vector<int> g[N],g1[N],g2[N];
vector<pii> qr[N];
int dx[5]={0,-1,0,1};
int dy[5]={-1,0,1,0};
string s[N];
bitset<N> bt[N];
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
inline int idx(int x,int y){
return (x-1)*m+y;
}
void dfs(int u,int f){
fa[find(u)]=find(f);
vis[u]=1;
for(int v:g[u]){
if(vis[v]) continue;
dfs(v,u);
}
}
stack<int> st;
void tar(int u){
dfn[u]=low[u]=++tot;vis[u]=1;
st.push(u);
for(int v:g1[u]){
if(!dfn[v]){
tar(v);
low[u]=min(low[u],low[v]);
}else if(vis[v]) low[u]=min(low[u],dfn[v]);
}if(low[u]==dfn[u]){
col++;
while(1){
int t=st.top();st.pop();
vis[t]=0;scc[t]=col;
if(u==t) break;
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("treasure.in","r",stdin);
freopen("treasure.out","w",stdout);
cin>>n>>m>>k>>qq;
rep(i,1,N-1) fa[i]=i;
rep(i,1,n){
cin>>s[i];s[i]=" "+s[i];
}rep(i,1,n){
rep(j,1,m){
if(s[i][j]=='#') continue;
rep(k,0,3){
int fx=dx[k]+i;
int fy=dy[k]+j;
if(fx<1||fy<1||fx>n||fy>m) continue;
if(s[fx][fy]=='#') continue;
g[idx(i,j)].pb(idx(fx,fy));
g[idx(fx,fy)].pb(idx(i,j));
}
}
}rep(i,1,n){
rep(j,1,m){
if(!vis[idx(i,j)]){
dfs(idx(i,j),idx(i,j));
}
}
}
rep(i,1,k){
int a,b,c,d;cin>>a>>b>>c>>d;
g1[find(idx(c,d))].pb(find(idx(a,b)));
}m(vis);
rep(i,1,n){
rep(j,1,m){
if(!dfn[find(idx(i,j))]&&s[i][j]!='#') tar(find(idx(i,j)));
}
}
rep(i,1,qq){
int a,b,c,d;cin>>a>>b>>c>>d;
qr[scc[find(idx(a,b))]].pb({scc[find(idx(c,d))],i});
}rep(i,1,n){
rep(j,1,m){
for(int v:g1[find(idx(i,j))]){
if(scc[find(idx(i,j))]!=scc[find(v)]){
g2[scc[find(idx(i,j))]].pb(scc[find(v)]);
d[scc[find(v)]]++;
}
}
}
}queue<int> q;
rep(i,1,col) if(!d[i]) q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
bt[u][u]=1;
for(pii t:qr[u]){
int v=t.fi,id=t.se;
if(bt[u][v]==1) ans[id]=1;
}for(int v:g2[u]){
d[v]--;bt[v]|=bt[u];
if(!d[v]) q.push(v);
}
}rep(i,1,qq) cout<<ans[i]<<'\n';
return 0;
}
t2
题意
小X曾经有一个神奇的字符串 \(S\),\(S\) 每一个字符都是一个非负整数。
将 \(S\) 的字符从 \(1\) 开始依次编号,设 \(n = |S|\),用 \(S_{i, j}\) 表示 \(S\) 的第 \(i\) 个字符到第 \(j\) 个字符构成的子串。
每条信息都是 \(x_i, y_i, z_i\) 的形式,表示 \(\operatorname{LCP}( x_i, y_i) = z_i\)。
现在小X只想你找到所有满足条件的字符串中,字典序最小的一个。
第一行两个整数 \(n, m\),表示字符串长度,信息个数。
然后 \(m\) 行,每行三个整数 \(x_i, y_i, z_i\),描述一条信息。
对于所有数据点,满足 \(1 \leq m \leq 1000\)。
题解
诈骗题。
时间复杂度是 \(O(n^2)\) 的,考虑限制个数。发现每个 \(\operatorname{LCP}( x_i, y_i) = z_i\),就是在告诉你 \((x_i,x_i+z_i-1)=(y_i,y_i+z_i-1)\),和\((x_i+z_i,x_i+z_i)\ne(y_i+z_i,y_i+z_i)\)。
这个限制数也是 \(O(n^2)\) 的,所以可以并查集暴力维护出来所有位置颜色相不相同。然后就是贪心放每个颜色。具体的话用一个 \(bitset\) 维护连通性就好了 \(O(n^2+\frac{n^3}{w})\),实际上可以通过倍增+并查集来实现合并,不同的关系只有 \(m\) 条,可以直接暴力处理贪心。
const int N=1e3+10;
int n,m,k,T,a[N],vis[N],ans[N],fa[N];
bitset<N> f[N],now;
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}vector<pii> q;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin>>n>>m;
rep(i,1,n) fa[i]=i;
rep(i,1,m){
int x,y,z;cin>>x>>y>>z;
rep(j,0,z-1){
int fx=find(x+j),fy=find(y+j);fa[fx]=fy;
}if(y+z<=n&&x+z<=n) q.pb({x+z,y+z});
}for(pii t:q){
int x=find(t.fi),y=find(t.se);
if(x==y){
cout<<-1;return 0;
}f[x][y]=f[y][x]=1;
}rep(k,0,n){
now=0;
rep(i,1,n){
if(vis[i]||(now&f[find(i)])!=0) continue;
ans[i]=k;vis[i]=1;now[find(i)]=1;
}
}rep(i,1,n) cout<<ans[i]<<" ";
return 0;
}
t3
题意
小X有一台容量为 \(c\)的割草机。他决定将草坪分成 \(n\) 条通道,编号从 \(0\) 到 \(n-1\),他必须按此顺序割草。每条通道 \(i\) 含有未割草的数量 \(v[i]\),由于一些未知原因,小X推动割草机经过该通道需要 \(a[i]\) 秒。
在经过几条通道后,割草机可能会达到满载状态,此时它会停止割草,留下该通道上的一些草。每当这种情况发生时,需要清空其收集箱,这需要 \(b\) 秒,并且只能在通道末尾进行。如果收集箱在小X经过通道 \(i\) 时装满,他需要继续推动割草机直到通道末尾,清空收集箱,然后再次经过该通道(或根据需要多次)以割除剩余的草。例如,如果对于通道 \(i\) 我们需要通过 \(3\) 次才能清除所有草,那将花费 \(a[i] + b + a[i] + b + a[i]\) 秒。在割完整个草坪后,必须清空割草机。
经过大量思考和抱怨完成割草将花费他太多时间后,小X得出结论,有时在收集箱达到满载之前清空它可能更省时,但他不确定可以使用的最佳策略。因此,他请求你的帮助。
给定每条通道上的草量和推动割草机经过每条通道所需的时间,收集箱的容量和清空它所需的时间,找出小X以最短时间完成割草的最佳方式。
对于所有测试数据,满足: \(1 \leq n \leq 200000\),对于每个 \(0 \leq i < n\), \(1 \leq a[i], v[i] \leq 10^{9}\), \(1 \leq b, c \leq 10^{9}\),保证正确答案不超过 \(10^{18}\)。
题解
假交互,真难题。
考虑一个显然的 \(dp\),\(f_{i,j}\) 表示割完前 \(i\) 个草坪剩下 \(j\) 时间的最小时间。发现状态数很难下降,考虑如何快速转移。
考虑 $a _ { i } $, 设 \(s u m\) 表 示初始箱子余量为0时只考虑打扫完 \(a _ { i }\) 至少需要多少时间。
当 \(a _ { i }\) 为 \(c\) 的倍数时,显然有 \(d p _ { i - 1 , 0 } + s u m \rightarrow d p _ { i , 0 } 。\)
对于 $1 \leq j \leq c - 1 $, 有 \(d p _ { i - 1 , j } + s u m + a _ { i } \rightarrow d p _ { i , j } ,\) 因为相比与余量 \(0\),需要多向草坪跑一次清完最后一点。
当 \(a _ { i }\) 不为 \(c\) 的倍数时,对于 \(0 \leq j \leq c - \left( v _ { i } \bmod c \right) - 1 ,\) 有 \(dp_{i−1,j} + sum\rightarrow dp_{i,j + (v_{i}~mod ~c)}\) , 因为清未割草次数相同。 对于 $j = c - \left( v _ { i } \bmod c \right) $,有 $d p _ { i - 1 , j } + s u m + b \to d p _ { i , 0 } $,相比上一项要多清理一次未割草。
对于 \(c - \left( v _ { i } \bmod c \right) + 1 \leq j \leq c - 1 ,\) 有 \(d p _ { i - 1 , j } + s u m + b + a _ { i } \rightarrow d p _ { i , j-c - \left( v _ { i } \bmod c \right) } ,\) 相比上一项多跑一次草坪。 最后就是 \(\min \left\{ d p _ { i , j } \right\} + b \rightarrow d p _ { i , 0 } ,\) 因为任意容量都可以清空。
我们发现这个转移被分为了至多三段,于是可以动态偏移下标 的位置,再用上区间加,全局最小值和
单点查询操作。
然后这些用线段树就可以全部做完啦,时间复杂度\(O(n\log V)\) ,因为要动态开点。
细节:
- 为什么可以动态开点,而且同时有些标记没有下传:
- 因为那些目前没有出现在可能的答案时一定不优,证明为什么只在过完草坪后才清除未满的草最优即可。
- 因为他有区间转移的大小是相同的特点,所以可以采用优化转移这种做法。
#include "strategy.h"
#include "grader.cpp"
#include<bits/stdc++.h>
using namespace std;
int n,c,b,sum,root=1,cnt=1;
const int N=1e6+10;
struct node{
int ls,rs;
long long minx,tag;
inline node():minx(0x3f3f3f3f3f3f3f3f){}
inline void addtag(long long x){
minx+=x,tag+=x;
}
}d[N<<2];
inline void push_down(int v){
if(d[v].tag){
if(d[v].ls)d[d[v].ls].addtag(d[v].tag);
if(d[v].rs)d[d[v].rs].addtag(d[v].tag);
d[v].tag=0;
}
}
void modify1(int &v,int l,int r,int w,long long x){
if(!v) v=++cnt;
if(l==r){
d[v].minx=min(x,d[v].minx);return;
}int mid=(l+r)>>1;
push_down(v);
w<=mid?modify1(d[v].ls,l,mid,w,x):modify1(d[v].rs,mid+1,r,w,x);
d[v].minx=min(d[d[v].ls].minx,d[d[v].rs].minx);
}
long long query(int &v,int l,int r,int w){
if(l==r) return d[v].minx;
int mid=(l+r)>>1;push_down(v);
if(w<=mid) return query(d[v].ls,l,mid,w);
else return query(d[v].rs,mid+1,r,w);
}
inline void modify2(int L,int R,long long x,int v=1,int l=0,int r=c-1){
if(!v) return;
if(L==l&&R==r) return d[v].addtag(x),void();
int mid=(l+r)>>1;
push_down(v);
if(L<=mid) modify2(L,min(R,mid),x,d[v].ls,l,mid);
if(R>mid) modify2(max(L,mid+1),R,x,d[v].rs,mid+1,r);
d[v].minx=min(d[d[v].ls].minx,d[d[v].rs].minx);
}
void modify(int l,int r,long long x){
if(l>r) return;
l=(l+sum)%c,r=(r+sum)%c;
if(l>r) modify2(0,r,x),modify2(l,c-1,x);
else modify2(l,r,x);
}
long long mow(int N,int C,int B,vector<int>&a,vector<int>&v){
n=N,c=C,b=B;
modify1(root,0,c-1,0,0);
for(int i=0;i<n;i++){
long long val=(v[i]-1)/c+1;
if(v[i]%c==0){
modify(0,0,val*(a[i]+b));
modify(1,c-1,val*(a[i]+b)+a[i]);
}else{
modify(0,c-v[i]%c-1,val*(a[i]+b)-b);
modify(c-v[i]%c,c-v[i]%c,val*(a[i]+b));
modify(c-v[i]%c+1,c-1,val*(a[i]+b)+a[i]);
}long long tmp=d[1].minx+b;sum=((sum-v[i])%c+c)%c;
modify1(root,0,c-1,sum,tmp);
}
return query(root,0,c-1,sum);
}
t4
题意
有 \(n\) 种石子,第 \(i\) 种石子有 \(a_i\) 颗,保证 \(\sum a_i \equiv 0 \pmod{2}\)。
首先,Alice需要将石子分成数目相等的两堆,即,两堆石子大小均为 \(\frac{\sum a_i}{2}\)。
接着,第一轮中,在分出的第一堆石头上,两个人交替取石子,每次只能取一颗,Alice先取,直到石子被取完为止。
然后,第二轮中,在分出的第二堆石头上,两个人交替取石子,每次只能取一颗,Bob先取,直到石子被取完为止。
最终,Alice在每一轮结束后,分别根据这一轮自己手中持有的石子来计算分数,然后求和。
每一轮具体的得分计算方法为:
- 对于第 \(i\) 种石子,若 Alice 手中有 \(c_i\) 颗,那么他将获得 \(\left\lfloor\frac{c_i}{p_i}\right\rfloor \times val_i\) 的分数。
Alice的目标是让自己的分数总和尽可能高,而Bob想让Alice的分数总和尽可能低。你觉得这个游戏很有趣,你想要知道,若两人都绝顶聪明,Alice最终将得到多少分。
对于所有数据,有 \(1 \leq n \leq 2000\),\(\sum a_i \leq 2 \times 10^6\),\(\sum p_i \leq 2000\),\(val_i \leq 3000\),\(\sum a_i \times val_i \leq 2 \times 10^9\),保证 \(a_i, p_i, val_i \geq 1\),保证 \(\sum a_i \equiv 0 \pmod{2}\)。
题解
Sub 1
直接计算即可。
时间复杂度 \(O ( 1 )\)
Sub 2
考虑枚举第一个人怎么分,然后分出来以后暴力枚举两人的取石子方案做搜索,一堆石子量至多只有4 颗,因此可以轻松跑过。 时间复杂度 \(O ( ? )\)
Sub 3
仍然考虑枚举第一个人怎么分,设 \(d p [ s t a t e 1 ] [ s t a t e 2 ] [ 0 / 1 ]\) 表示当前还剩下的石子为 $s t a t e 1 $, 第一个人拿了的石子状态为 $s t a t e 2 $, 这一步该第一个人走/第二个人走,在这一步按照最优决策方案的情况下第一个人最终会得多少分
考虑时间复杂度,由于石子种类数不会太大(至多为10),状态数总量也只有2000000左右,于是进行记忆化搜索的极限时间复杂度就是 $O ( 2 e 7 ) $, 可以跑过。 时间复杂度 \(O \left( \left( \prod \left( a _ { i } + 1 \right) \right) ^ { 2 } n \right)\)
Sub 4
我们来重新审视一下这个游戏。
假设一组分组方案分出的每种石头分别有 \(b _ { i }\) 个。
我们称一种石子是"特殊的”,当且仅当 \(b _ { i } \equiv - 1 \left( \bmod 2 \times p _ { i } \right)\)
考虑到,如果每一次第二个人都复读第一个人的策略,那么有如下的情况:
对于不特殊的石子,第二个人可以通过模仿第一个人的决策,来让第一个人只拿到 \(\left| \frac { b _ { i } } { 2 p _ { i } } \right| \times v a l _ { i }\) 的分数
而对于特殊的石子,如果让第一个人取到了第一颗,则第一个人能获得 \(\left( \left[ \frac { b _ { i } } { 2 p _ { i } } \right] + 1 \right) \times v a l _ { i }\) 的分数, 否则第一个人只能拿到 \(\left| \frac { b _ { i } } { 2 p _ { i } } \right| \times v a l _ { i }\) 的分数。
本质上讲,特殊石子就是第一个人拿没拿到第一颗会影响第一个人获得分数的石子,而非特殊石子就是不论拿到第一颗的是先手还是后手都不会影响分数的石子。 于是我们有这样一种策略:
如果场上存在特殊的石子,那么优先拿走一颗分数最高的特殊的石子,这样若当前决策者为第一个人, 则能获得的分数最高(特殊石子就相当于多出来的分数),若当前决策者为第二个人,则能阻止第一个人获得的额外分数最多;当特殊的石子被拿掉一颗之后,我们就再也不认为它属于特殊石子了。
令特殊石子的收益从大到小排序以后为 $v _ { 1 } , v _ { 2 } , \cdots , v _ { n } $。 对于非特殊石子,以及被拿掉一颗的特殊石子,第一个人总能通过某种方法拿到每种石子中的一半石子,而第二个人也总能通过复读阻止第一个人拿到超过一半石子。
所以,若一开始是第一个人先手,第一个人将获得 \(\sum \left[ \frac { b _ { i } } { 2 p _ { i } } \right] + v _ { 1 } + v _ { 3 } + v _ { 5 } + \cdots\) 的分数,若一开始是第二个人先手,第一个人将获得 \(\sum \left[ \frac { b _ { i } } { 2 p _ { i } } \right] + v _ { 2 } + v _ { 4 } + v _ { 6 } + \cdots\) 的分数。
将所有石子的 \(v a l\) 从大到小排序,这样若当前加入石子是特殊石子,那么这种石子一定是现存所有特殊石子中分数最小的。
考虑设 \(d p [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] ]\) 表示分配了前 \(i\) 种 石子,当前第一堆石子中总共有 \(j\) 颗石子,第一堆石子中的特殊石子的奇偶性是 $0 / 1 $, 第二堆石子中的特殊石子的奇偶性是 $0 / 1 $, 第一个人到目前能取得的最大分数是多少。
转移就是枚举当前这种石子放多少颗,然后按照上面的式子计算第一个人将从这堆石子得到的分数是多少即可。 最后的答案就是 \(\max \left\{ d p [ n ] \left[ \sum a _ { i } / 2 \right] [ 0 / 1 ] [ 0 / 1 ] \right\} 。\)
时间复杂度为 \(O \left( \left( \sum a _ { i } \right) ^ { 2 } \right)\)
Sub 5
\(\sum a _ { i }\) 很大但是有一个奇怪的式子的和很小。
观察到假设你向第一堆石子里放入了 \(b _ { i }\) 个 第 \(i\) 种石子,那么你向第一堆石子中放入 \(b _ { i } + 2 p _ { i }\) 个第 \(i\) 种石子是不会影响最终分数的。
看到数据范围中 \(\sum p _ { i }\) 比较小,不妨考虑将 \(d p\) 的第二维变成 \(" b _ { i } \bmod 2 \times p _ { i } ^ { n }\) 的和,这样在做 \(d p\) 时我们就不需要考虑枚举这种石头放了多少个,而是枚举这种石头模 \(2 p _ { i }\) 余多少个。
设你向第一堆石子中放入了的第 \(i\) 种石子模上 \(2 \times p _ { i }\) 的余数为 \(r _ { i }\) (就是你 \(d p\) 枚 举的东西),而第 \(i\) 种石子一共有 \(a _ { i }\) 颗。
我们称”一组第 \(i\) 种石子"为 \(2 \times p _ { i }\) 颗第 \(i\) 种石子,上文的结论就是:我们将一组石子从第一堆移动到第二堆,第一个人能获得的分数是不变的。 那么,当 \(r _ { i } \leq a _ { i } \bmod \left( 2 \times p _ { i } \right)\) 的时候,你可以自由分配 \(\left| \frac { a _ { i } } { 2 p _ { i } } \right|\) 组石头;
当 \(r > a _ { i } \bmod \left( 2 \times p _ { i } \right) ,\) 你就仅能自由分配 \(\left| \frac { a _ { i } } { 2 p _ { i } } \right| - 1\) 组石头。
如果同时存在两种情况,是很蛋疼的。不妨在枚举 \(r _ { i }\) 的时候,当 \(r _ { i } \leq a _ { i } \bmod \left( 2 \times p _ { i } \right) ,\) 同时去考虑放入 \(r _ { i }\) 颗石子和 \(r _ { i } + 2 p _ { i }\) 颗石子的情况,这样最后能自由分配的石头就统一为 \(\left| \frac { a _ { i } } { 2 p _ { i } } \right| - 1\) 组了。 这前半段 \(d p\) 的复杂度是 \(O \left( \left( \sum p _ { i } \right) ^ { 2 } \right) 。\)
接着我们要解决的就是,有 \(n\) 种石头,第 \(i\) 种 石头可以使用0到 \(\left| \frac { a _ { i } } { 2 p _ { i } } \right| - 1\) 组,问能不能凑出 \(0 , 1 , 2 , \cdots , \frac { \sum a _ { i } } { 2 }\) 颗石头。
这是一个背包问题,如果直接背包的话首先你要枚举石头的种类,接着你要枚举已经放了多少石子,这一维大小是 $\sum a _ { i } $, 最后你要枚举当前石头放多少组,这一维大小是 $\frac { a _ { i } } { 2 p _ { i } } $。 因此最终时间复杂度为 \(O \left( \left( \sum p _ { i } \right) ^ { 2 } + \left( \sum a _ { i } \right) \left( \sum \frac { a _ { i } } { 2 p _ { i } } \right) \right)\)
结合Subtask4的算法可以得到60分。
Sub6
考虑继续优化Subtask5的第二部分的计算。
设 \(f [ i ] [ j ]\) 表 示已经使用了前 \(i\) 种石子,现在石子堆里已经有了 \(j\) 个 石子,最少需要用到多少组第 \(i\) 种 石子。 若不能凑出 \(j\) 个石子,则 \(f [ i ] [ j ] = - 1\)
转移显然:若 $f [ i - 1 ] [ j ] \neq - 1 $, 则 $f [ i ] [ j ] = 0 $;
否则,若 \(f [ i ] \left[ j - 2 \times p _ { i } \right] < \left[ \frac { a _ { i } } { 2 p _ { i } } \right] - 1\) 且 \(f [ i ] \left[ j - 2 \times p _ { i } \right] \neq - 1 ,\) 则
[f [ i ] [ j ] = f [ i ] \left[ j - 2 \times p _ { i } \right] + 1 ;]
否则 $f [ i ] [ j ] = - 1 $。
这样我们顺利地将第二部分的 \(d p\) 优化到了 \(O \left( n \times \sum a _ { i } \right) 。\)
结合Subtask4的算法可以得到70分。
或者还有这样一种方法:
有一个经典问题是,有1000个苹果,你有十个篮子,你需要将苹果放进这十个篮子里,使得你总能选出其中一些篮子,使得篮子里的苹果个数和为 \(i , 1 \leq i \leq 1 0 0 0\) 这个问题的解是将苹果分成:1,2,4,8,16,32,64,128,256,489个,具体证明很简单,略。
所以你可以把 \(\frac { a _ { i } } { 2 p _ { i } }\) 按照类似的方式拆分成 \(\log g\) 个物品然后背包。
用bitset加速就可以通过
。时间复杂度是 \(O \left( \frac { n \left( \sum a _ { i } \right) \left( \log \sum a _ { i } \right) } { 3 2 } \right)\)
Sub 7
第一部分的计算已经相当优秀了,无需优化。
我们来继续优化第二部分的计算。
\(f\) 计算的瓶颈在于,对于每一种石头,我们都需要去遍历一遍之前放了多少石头。
考虑将 \(p _ { i }\) 相同的石头合并。
令人惊喜的是,不同的 \(p _ { i }\) 至多只有 \(\sqrt { \sum p _ { i } }\) 种。
对于同样的 $p _ { i } $, 我们统计这些石子一共可以放多少组,然后像S u b t a s k 6一样转移 \(f\) 即可。
最终的时间复杂度为 \(O \left( \left( \sum p _ { i } \right) ^ { 2 } + \left( \sqrt { \sum p _ { i } } \sum a _ { i } \right) \right) ,\) 可以通过 \(1 0 0 \%\) 的数据。
与Subtask6相同,同样可以使用bitset,同样可以通过。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct P{
int a,p,v;
}a[2005];
int n,f[2][8005][2][2],t[4005],b[40005],N,S=0,sum=0,o=0,c=1;
bitset<1000005> g;
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
cin>>n,memset(f,0xc0,sizeof(f)),f[0][0][1][0]=0;
for(int i=1;i<=n;i++)cin>>a[i].a>>a[i].p>>a[i].v,S+=a[i].a;
sort(a+1,a+n+1,[](P x,P y){return x.v>y.v;});
for(int i=1;i<=n;i++,c^=1){
memset(f[c],0xc0,sizeof(f[c]));
int w=min(a[i].a%(2*a[i].p)+2*a[i].p,a[i].a);
t[i]=(a[i].a-w)/(2*a[i].p),sum+=t[i]*a[i].v,o+=w;//有 t[i] 组自由分配
for(int j=0;j<=o;j++){
for(int k=0;k<=w&&k<=j;k++){
int p1=(k%(2*a[i].p)==2*a[i].p-1),p2=((w-k)%(2*a[i].p)==2*a[i].p-1);
for(int x=0;x<2;x++){
for(int y=0;y<2;y++){
f[c][j][x][y]=max(f[c][j][x][y],f[c^1][j-k][x^p1][y^p2]+k/(2*a[i].p)*a[i].v
+(w-k)/(2*a[i].p)*a[i].v+p1*(x^p1)*a[i].v+p2*(y^p2)*a[i].v);
}
}
}
}
}
for(int i=1;i<=n;i++){
int j=1;
while(j<t[i])b[++N]=2*j*a[i].p,t[i]-=j,j<<=1;
b[++N]=2*t[i]*a[i].p;
}
g[0]=1;
for(int i=1;i<=N;i++)g|=g<<b[i];
int ans=0;
for(int i=0;i<=S/2;i++)if(g[i]&&S/2-i<=o)for(int x=0;x<2;x++)for(int y=0;y<2;y++)ans=max(ans,f[c^1][S/2-i][x][y]);
cout<<ans+sum;
}

浙公网安备 33010602011771号