[Codeforces Round 857 (Div. 1)][Codeforces 1801A~1801G(部分,已更新A~D、F)]
FST哩,好似!本来能+80的,现在只加了30,相当于掉了50分捏
1801A - The Very Beautiful Blanket
题目大意:要求构造一个 \(n\times m\) 的矩阵 \(B\),使得对任意一个 \(4\times 4\) 的子矩阵 \(A\) 均满足 \(A_{13} \oplus A_{14} \oplus A_{23} \oplus A_{24} = A_{31} \oplus A_{32} \oplus A_{41} \oplus A_{42}\) 且 \(A_{13} \oplus A_{14} \oplus A_{23} \oplus A_{24} = A_{31} \oplus A_{32} \oplus A_{41} \oplus A_{42}\)
直接令 \(A_{ij}=256\times i+j\) 即可,原理是这样构造可以让任意一个 \(2\times 2\) 的子矩阵满足异或和为零,因为在二进制下对应权值最低的八位代表列号,更高位代表行号,各部分都是能各自消掉的。
#include<bits/stdc++.h>
using namespace std;
#define N 300
int T,n,m,a[N][N];
int main()
{
for(int i=0;i<256*256;i++){
int x=i/256,y=i%256;
a[x][y]=i;
}
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
printf("%d\n",n*m);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
printf("%d%c",a[i][j],j<m-1?' ':'\n');
}
}
1801B - Buying gifts
题目大意:有 \(n\) 个物品,每个物品有两个权值 \(a_i,b_i\)分别表示该物品给 \(A,B\) 两个人的权值。要求把这 \(n\) 个物品分配给两人使得 \(|maxA-maxB|\) 尽可能小。\(maxA,maxB\) 分别表示两人分配到物品的最大权值,每人至少要被分配一个物品。
这题我 FST 了,原因是当所有物品一样的时候我全买给了 \(A\),好似!
这种题的做法有一个惯用套路就是固定其中一维然后求解。假设我们钦定了 \(maxA\) 的值,那么可以发现所有 \(a_i>maxA\) 的物品都必须给 \(B\),而这时其它物品怎么选都不会影响 \(maxA\) 的值,那么在这些物品中选一个 \(b_i\) 与 \(maxA\) 最接近的给 \(B\) 即可。
于是就得到了一种做法,先把物品按照 \(a_i\) 排序,从小到大枚举钦定哪一个物品是必须给 \(A\) 的,这时后面的所有物品就都得给 \(B\),可以预处理后缀 \(\max\) 求出这一部分对 \(maxB\) 的贡献。而对于前面选离 \(maxA\) 最近的值,利用 \(\mathrm{multiset}\) 中的 \(\mathrm{lower\_bound}\) 乱搞即可。
注意由于 \(a_i\) 的值可能相同,所以需要对相同的 \(a_i\) 分批处理,具体实现见代码。
\(\mathrm{Find}\) 函数中的 \(o\) 就是特判之前 FST 的情况用的。
#include<bits/stdc++.h>
using namespace std;
#define N 500010
int n,f[N],ans;
multiset<int>s;
struct rua
{
int x,y;
void read(){scanf("%d%d",&x,&y);}
bool operator <(const rua &t)const{return x<t.x;}
}a[N];
int Find(int x,int y,int o)
{
int res=o?1e9:abs(x-y);
auto it=s.lower_bound(x);
if(it!=s.end()){
int z=max(y,*it);
res=min(res,abs(x-z));
}
if(it!=s.begin()){
it--;
int z=max(y,*it);
res=min(res,abs(x-z));
}
return res;
}
void init()
{
ans=2e9;
s.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++)a[i].read();
sort(a+1,a+n+1);
f[n+1]=0;
for(int i=n;i>=1;i--)f[i]=max(a[i].y,f[i+1]);
for(int i=1,j;i<=n;i=j+1){
j=i;
while(j<=n && a[j].x==a[i].x)j++;j--;
int x=a[i].x;
int y=f[j+1];
for(int k=i;k<=j;k++)s.insert(a[k].y);
for(int k=i;k<=j;k++){
s.erase(s.find(a[k].y));
ans=min(ans,Find(x,y,j==n));
s.insert(a[k].y);
}
}
printf("%d\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)init();
}
1801C - Music Festival
题目大意:定义一个序列 \(A\) 的价值为满足 \(A_i=\max_{j=1}^{i}A_j\) 的 \(i\) 的个数。给定 \(n\) 个序列,要求以任意方式将他们拼接起来,使得得到的序列价值最大。
首先可以得到,对于每个序列,只有满足对应性质的那些元素才有可能对答案产生贡献,所以首先可以在输入的时候就对序列进行预处理,留下一个严格递增的序列。
考虑 \(\mathrm{DP}\),令 \(f_i\) 表示以数字 \(i\) 为结尾的序列的最大价值,那么可以得到如果把某个序列 \(A\) 接在尾部,对应的方案只能更新到 \(f_{maxA}\) 上。于是考虑把序列按照最后一个元素大小进行排序。
在 \(\mathrm{DP}\) 的过程中,考虑对每个序列枚举该序列是从第几个位置开始产生贡献的。假设当前在序列 \(A\) 中枚举到的位置为 \(i\)(这题本人实现下标是从 \(0\) 开始),序列处理后的长度为 \(k\),最大值为 \(m\),那么找到最大的 \(j\) 使得 \(j<a_i\) 且 \(f_j\) 有值,此时 \(f_j\) 就会对 \(f_m\) 产生 \(f_j+k-i\) 的贡献,实时更新答案即可。\(j\) 的查找可以通过记录所有当前 \(f\) 有值的位置,\(\mathrm{lower\_bound}\) 一下即可。最后不要忘了多测清空。
#include<bits/stdc++.h>
using namespace std;
#define N 200010
int T,n,f[N];
struct rua
{
int m,k;
vector<int>d;
void init(){m=k=0;d.clear();}
void read(){
d.clear();
scanf("%d",&k);
for(int i=1;i<=k;i++){
int x;
scanf("%d",&x);
if(x<=m)continue;
d.push_back(x);
m=x;
}
k=d.size();
}
bool operator <(const rua &t)const{return m<t.m;}
}a[N];
vector<int>v;
void init()
{
int ans=0;
v.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++){
a[i].init();
a[i].read();
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
for(int j=0;j<a[i].k;j++){
int x=a[i].d[j];
int k=lower_bound(v.begin(),v.end(),x)-v.begin()-1;
f[a[i].m]=max(f[a[i].m],a[i].k-j);
if(k>=0)f[a[i].m]=max(f[a[i].m],f[v[k]]+a[i].k-j);
ans=max(ans,f[a[i].m]);
}
v.push_back(a[i].m);
}
for(auto i:v)f[i]=0;
printf("%d\n",ans);
}
int main()
{
scanf("%d",&T);
while(T--)init();
}
1801D - The way home
题目大意:一个 \(n\) 个点 \(m\) 条边的有向图,每条边有对应花费。某个憨憨演唱家现在身上只有 \(p\) 元钱,他可以选择在某个点 \(i\) 开演唱会以得到 \(s_i\) 的收入。问要从 \(1\) 走到 \(n\) 所需要举办的演唱会次数最小是多少。
首先考虑如果回家的路径确定了,唱歌的决策是怎样的。可以发现最后肯定是在某个点 \(x\) 死命唱歌直到恰好攒够回家的钱,然后直接走最小花费的路径跑路。接着再倒着往回推,在这之前他肯定也是在另一个点恰好攒够钱后再动身来到 \(x\)。所以可以得到如果已经选定了先后在哪些位置开演唱会,那么其唱歌决策是确定的,即每次在当前位置一直举办演唱会直到攒够前往下一个位置的钱。
于是跑 \(n\) 遍单源最短路,对所有 \((i,j)\) 预处理出从点 \(i\) 走到点 \(j\) 的最小花费 \(dis_{i,j}\)。再使用类似最短路的过程贪心选取下个演唱会的地点。那么在这一部分的最短路中,有两个参考值,一个是 \(cost_i\) 表示到达 \(i\) 这个点需要举办演唱会的最小次数,另一个是 \(res_i\) 表示在次数最小的前提下,所剩余钱数的最大值。那么根据这两个信息跑最短路即可,每次到点 \(x\) 时,枚举下一个唱歌的位置 \(i\),根据当前的 \(res_x\) 和 \(dis_{x,i}\) 的差值以及在 \(x\) 举办演唱会的收入 \(s_x\) 可以得到需要举办演唱会的次数 \(k\),直接转移即可。最后答案就是 \(cost_n\)。
#include<bits/stdc++.h>
using namespace std;
#define N 810
#define LL long long
int T,n,m,p,b[N],vis[N];
LL dis[N],cost[N],res[N];
vector<pair<int,int>>d[N];
struct rua
{
int x;
LL cost,res;
bool operator <(const rua &t)const{
if(cost!=t.cost)return cost<t.cost;
return res>t.res;
}
};
set<rua>s;
void Dij(int x)
{
set<pair<LL,int>>q;
for(int i=0;i<=n;i++)dis[i]=1e18;
dis[x]=0;
for(int i=1;i<=n;i++)q.insert(make_pair(dis[i],i));
while(!q.empty()){
auto PI=*q.begin();
q.erase(q.begin());
int x=PI.second;
for(auto pi:d[x]){
LL nd=dis[x]+pi.second;
int y=pi.first;
if(nd<dis[y]){
q.erase(make_pair(dis[y],y));
dis[y]=nd;
q.insert(make_pair(dis[y],y));
}
}
}
}
LL ceil(LL x,LL y)
{
if(x<0)return 0;
if(x%y==0)return x/y;
return x/y+1;
}
void init()
{
s.clear();
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++){
d[i].clear();
scanf("%d",&b[i]);
vis[i]=0;
cost[i]=1e18;
res[i]=0;
}
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
d[u].push_back(make_pair(v,w));
}
Dij(1);
if(dis[n]==dis[0]){
printf("-1\n");
return;
}
cost[1]=0,res[1]=p;
s.insert((rua){1,cost[1],res[1]});
while(!s.empty()){
auto X=*s.begin();
s.erase(s.begin());
int x=X.x;
vis[x]=1;
Dij(x);
for(int i=1;i<=n;i++)if(!vis[i]){
LL k=ceil(dis[i]-res[x],b[x]);
rua tmp={i,cost[x]+k,res[x]+k*b[x]-dis[i]};
rua I={i,cost[i],res[i]};
if(tmp<I){
s.erase(I);
cost[i]=tmp.cost;
res[i]=tmp.res;
s.insert(tmp);
}
}
}
printf("%lld\n",cost[n]);
}
int main()
{
scanf("%d",&T);
while(T--)init();
}
赛场上为了省空间写了个类似于最短路套最短路的玩意orz
1801E - Gasoline prices
不会捏,留坑待填
1801F - Another n-dimensional chocolate bar
题目大意:给定一个长度为 \(n\) 的数组 \(a\) 和正整数 \(k\),要求构造一个数组 \(b\) 满足 \(1\le b_i \le a_i\) 且 \(\prod_{i=1}^{n}b_i \ge k\),使得 \(\lfloor \frac{a_1}{b_1} \rfloor \dotsm \lfloor \frac{a_n}{b_n} \rfloor \cdot \frac{1}{a_1 a_2 \cdots a_n} \cdot k\) 尽可能大,输出这个最大值。
由于 \(a_i\) 和 \(k\) 已经给出,所以就相当于要在满足条件的前提下最大化 \(\prod_{i=1}^{n}\lfloor \frac{a_i}{b_i} \rfloor\)。考虑一个朴素的 \(\mathrm{DP}\),\(f_{i,j}\) 表示当前到第 \(i\) 个位置,对应 \(b_i\) 的前缀乘积为 \(j\) 时,\(\lfloor \frac{a_i}{b_i} \rfloor\) 乘积的最大值。这样直接做复杂度爆炸。
接着考虑优化,因为要求的是 \(\prod b_i\ge k\),所以不妨把 \(\mathrm{DP}\) 改成令 \(f_{i,j}\) 表示到第 \(i\) 个位置,要求剩下 \(b_i\) 乘积 \(>j\) 的答案,这样转移的时候 \(j\) 的值大概就能减少的很快,每次枚举 \(b_i\) 的值就能直接从 \(j\) 转移到 \(\lfloor\frac{j}{b_i}\rfloor\) 上。从 \(f_{1,k-1}\) 一路转移到 \(f_{n,0}\) 就是答案。
这时发现在转移过程中,实际上所有的 \(j\) 必然等于 \(\lfloor\frac{k-1}{x}\rfloor\)(\(x\) 为某个正整数),于是考虑数论分块预处理出所有可能的 \(j\),在这些位置间转移即可,显然这样的 \(j\) 只有 \(O(\sqrt k)\) 个。而每次 \(i,j\) 能转移到的位置数则是 \(O(\sqrt j)\) 个的,所以直接转移的总复杂度与杜教筛类似,为 \(O(n\cdot k^{\frac{3}{4}})\)。
#include<bits/stdc++.h>
using namespace std;
#define N 110
#define ld long double
int n,m,k,x,a[N],b[N*N],id[1<<24];
ld f[N][N*N],ans=1;
int Fl(int x,int y){return x/y;}
int main()
{
scanf("%d%d",&m,&k);
ans*=k,k--;int kk=k;
for(int i=1;i<=m;i++){
scanf("%d",&x);
if(x>1)a[++n]=x;
ans/=x;
kk/=x;
}
if(kk)return printf("0\n"),0;
m=0;
for(int l=1,r;l<=k;l=r+1){
r=k/(k/l);
b[++m]=r;
id[r]=m;
}
f[0][m]=1;
for(int i=1;i<=n;i++){
f[i][0]=f[i-1][0]*a[i];
for(int j=1;j<=m;j++)if(f[i-1][j]){
x=b[j];
for(int l=1,r;l<=a[i];l=r+1){
int nxt=x/l;
if(nxt)r=x/nxt;else r=a[i];
f[i][id[nxt]]=max(f[i][id[nxt]],f[i-1][j]*Fl(a[i],l));
}
}
}
printf("%.10Lf\n",f[n][0]*ans);
}