联赛模拟测试9
A. 嚎叫响彻在贪婪的厂房
分析
玄学题目,玄学题面,考场没写判重直接抱零。
因为要求一段连续的数是一个等差数列的子序列,那么容易想到这段序列里每两个数的差的绝对值一定都不是互质的,所以 \(O(n)\) 扫一遍就好了。当遇到重复的数或者差互质的时候,就清空用来判重的东西,然后 \(ans++\) 就行了。判重的话 \(set\) 和 \(map\) 随意。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
#define ll long long
const int maxn = 1e5+10;
int a[maxn];
map<int,int>mp;
inline int gcd(int x,int y){
if(y == 0)return x;
return gcd(y,x%y);
}
int main(){
int ans = 1;
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i]);
}
int d2 = abs(a[2] - a[1]);
mp[a[1]] = 1;
for(int i = 2;i <= n;++i){
int d = abs(a[i] - a[i-1]);
if(mp[a[i]]){//判重
mp.clear();//清空
ans++;
d2 = abs(a[i] - a[i+1]);//更新差值
mp[a[i]] = 1;
continue;
}
d2 = gcd(d,d2);
mp[a[i]] = 1;
if(d2 <= 1){//互质
ans++;
mp.clear();
mp[a[i]] = 1;
d2 = abs(a[i] - a[i+1]);
}
}
printf("%d\n",ans);
}
B. 征途堆积出友情的永恒
分析
考场把暴力分拿满了。 \(50\) 分写个 \(dp\) 就行了,剩下的 \(30\) 特判一下,瞎推一推就行。
其实 \(50\) 分的 \(dp\) 优化一下就是正解,考场没想,也应该想不出来。考虑朴素 \(dp\) ,\(f[i] = min(f[i],f[j] + max(sum[i]-sum[j],b[j]))\) 。考虑怎么表示后边的那个东西,后边那个可拆成两部分, \(f[j]+sum[i]-sum[j]\) 和 \(f[j]+b[j]\) ,然后我们要求的是最小值,而 \(j\) 是不定的, \(i\) 是确定的,所以我们可以只考虑 \(f[j]-sum[j]\) 和 \(f[j]+b[j]\) ,然后我们就可以进行优化。因为越小越好,所以我们开两个小根堆,分别存那两个值,设当前值的位置为 \(id\) ,那么当 \(id < i-k\) 时,直接 \(pop\) 掉,如果 \(f[j]+b[j]\) 这个堆的堆顶小于 \(f[j]-sum[j]\) 的堆顶,那么由于我们取的是最大值,前者肯定用不到了,所以我们就可以把它删掉,把那个位置的 \(f[id]-sum[id]\) 放到第二个堆里就行了。最后取两个堆顶的最小值更新答案。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
struct Node{
ll id,val;
Node(){}
Node(int x,int y){
id = x,val = y;
}
friend bool operator < (const Node& a,const Node& b){
return a.val > b.val;
}
};
priority_queue<Node>q1,q2;
const int maxn = 5e5+10;
int a[maxn],b[maxn];
ll sum[maxn],f[maxn];
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];
}
for(int i = 1;i <= n;++i){
scanf("%d",&b[i-1]);
}
memset(f,0x3f,sizeof(f));
f[0] = 0;
for(int i = 1;i <= n;++i){
q1.push(Node(i-1,f[i-1] + b[i-1]));
while(!q1.empty()){//判断是否能直接坐到这里
Node s = q1.top();
if(s.id < i - k)q1.pop();
else break;
}
while(!q2.empty()){
Node s = q2.top();
if(s.id < i - k)q2.pop();
else break;
}
while(!q1.empty()){//处理f[j]+b[j] 这个堆的堆顶小于 f[j]-sum[j] 的堆顶的情况
Node s = q1.top();
int j = s.id;
int val = s.val;
if(val > f[j] + sum[i] - sum[j])break;
q1.pop();
q2.push(Node(j,f[j]-sum[j]));
}
while(!q1.empty()){//同第1,2个while循环
Node s = q1.top();
if(s.id < i - k)q1.pop();
else break;
}
while(!q2.empty()){
Node s = q2.top();
if(s.id < i - k)q2.pop();
else break;
}
ll jl = 0x3f3f3f3f3f3f3f3f;
if(!q1.empty())jl = min(jl,q1.top().val);
if(!q2.empty())jl = min(jl,q2.top().val + sum[i]);
f[i] = jl;
}
printf("%lld\n",f[n]);
return 0;
}
80分代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn = 5e5+10;
int a[maxn],b[maxn];
ll f[maxn];
ll sum[maxn];
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];
}
int mx = 0;
for(int i = 1;i <= n;++i){
scanf("%d",&b[i-1]);
mx = max(mx,b[i-1]);
}
if(mx == 1){
printf("%lld\n",sum[n]);
return 0;
}
bool flag = 0;
for(int i = 1;i <= n;++i){
if(a[i] != b[i-1])flag = 1;
}
if(!flag){
printf("%lld\n",sum[n]);
return 0;
}
if(n == k){
printf("%lld\n",max((ll)b[0],sum[n]));
return 0;
}
if(n <= 10000 && k <= 1000){
memset(f,0x3f,sizeof(f));
f[0] = 0;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= min(i,k);++j){
int cost = sum[i] - sum[i-j];
if(cost <= b[i-j])cost = b[i-j];
f[i] = min(f[i],f[i-j] + cost);
}
}
printf("%lld\n",f[n]);
return 0;
}
return 0;
}
C. 小奇的仓库
分析
单纯求距离和的话直接换根 \(dp\) 就好了,关键在于异或 \(m\) ,发现 \(m\) 最大是 \(15\) ,那么也就是说只会对后四位进行更改,所以从这里入手。首先求出所有点到除它以外的所有点的距离(也就是 \(m\) 为 \(0\) 的情况)。然后先找一个根,记录一下树上每个节点上,子树中到它的距离后四位分别是 \(1\to 15\) 的点的个数,然后利用换根 \(dp\) 的思想,把这个只计算子树的个数拓展到当前节点之外所有点到当前点距离后四位是 \(1\to 15\) 的个数,然后最后计算答案的时候,只需要用当前这个节点不异或的答案,加上 \(1\to 15\) 每种情况异或后的值减去异或前的值再乘以到这个点后四位为异或前的值的个数即可。
30分换根dp
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int L = 1 << 20;
char buffer[L],*S,*T;
#define gc (S == T && (T = (S = buffer) + fread(buffer,1,L,stdin),S == T) ? EOF : *S++)
inline int read(){
int s = 0 ,f = 1;char ch = gc;
for(;!isdigit(ch);ch = gc)if(ch == '-')f = -1;
for(;isdigit(ch);ch = gc)s = s * 10 + ch - '0';
return s * f;
}
const int maxn = 1e5+10;
struct Node{
int v,next,val;
}e[maxn<<1];
int head[maxn],tot;
int dis[maxn],vis[maxn];
int n,m;
namespace Mis0{
const int maxn = 1e5+10;
int ans[maxn],dis[maxn],siz[maxn];
inline void Add(int x,int y,int z){
e[++tot].v = y;
e[tot].next = head[x];
e[tot].val = z;
head[x] = tot;
}
inline void dfs1(int x,int f){
siz[x] = 1;
for(int i = head[x];i;i = e[i].next){
int v = e[i].v;
if(v == f)continue;
dis[v] = dis[x] + e[i].val;
dfs1(v,x);
siz[x] += siz[v];
}
}
inline void dfs2(int x,int f){
for(int i = head[x];i;i = e[i].next){
int v = e[i].v;
if(v == f)continue;
ans[v] = ans[x] - siz[v] * e[i].val + (n - siz[v]) * e[i].val;
dfs2(v,x);
}
}
int main(){
for(int i = 1;i < n;++i){
int x = read(),y = read(),z = read() ^ m;
Add(x,y,z);
Add(y,x,z);
}
dfs1(1,0);
for(int i = 1;i <= n;++i){
ans[1] += dis[i];
}
dfs2(1,0);
for(int i = 1;i <= n;++i){
printf("%d\n",ans[i]);
}
return 0;
}
}
int main(){
n = read(),m = read();
Mis0::main();
return 0;
}
满分代码
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5+10;
struct Node{
int v,next,val;
}e[maxn<<1];
int head[maxn],tot,f[maxn],g[maxn][20],b[maxn][20];
int dis[maxn];
int siz[maxn];
int n,m;
inline void Add(int x,int y,int z){
e[++tot].v = y;
e[tot].next = head[x];
e[tot].val = z;
head[x] = tot;
}
inline void dfs1(int x,int fa){
b[x][0] = siz[x] = 1;
for(int i = head[x];i;i = e[i].next){
int v = e[i].v;
if(v == fa)continue;
dis[v] = dis[x] + e[i].val;
dfs1(v,x);
siz[x] += siz[v];
for(int j = 0;j <= 15;++j){
int k = (j + e[i].val) % 16;
b[x][k] += b[v][j];
}
}
}
inline void dfs2(int x,int fa){
for(int i = head[x];i;i = e[i].next){
int v = e[i].v;
if(v == fa)continue;
f[v] = f[x] - siz[v] * e[i].val + (n - siz[v]) * e[i].val;;
for(int j = 0;j <= 15;++j){
int k = (j + e[i].val) % 16;
g[v][k] = g[x][j] + b[v][k] - b[v][((j - e[i].val + 16) % 16 + 16) % 16];
}
dfs2(v,x);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i < n;++i){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
Add(x,y,z);
Add(y,x,z);
}
dfs1(1,0);
for(int i = 1;i <= n;++i){
f[1] += dis[i];
}
for(int i = 0;i <= 15;++i){
g[1][i] = b[1][i];
}
dfs2(1,0);
for(int i = 1;i <= n;++i){
g[i][0]--;
for(int j = 0;j <= 15;++j){
f[i] += ((j ^ m) - j) * g[i][j];
}
printf("%d\n",f[i]);
}
return 0;
}
D. 放置机器人
分析
二分图,如果没有墙的话直接行和列之间建边,然后匈牙利算法就行了。有墙的话就从墙把某行或某列分开,多几个标号和节点就行了。
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2500+10;
const int maxm = 60;
int m,n;
struct Node{
int v,next;
}e[maxn<<1];
char s[100][100];
int match[maxn];
int vis[maxn];
int bel1[maxm][maxm],cnt1;
int bel2[maxm][maxm],cnt2;
int head[maxn],tot;
inline void Add(int x,int y){
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
}
inline int dfs(int x){
vis[x] = 1;
for(int i = head[x];i;i = e[i].next){
int v = e[i].v;
if(vis[v])continue;
vis[v] = 1;
if(match[v] == -1 || dfs(match[v])){
match[v] = x;
return 1;
}
}
return 0;
}
int main(){
memset(match,-1,sizeof(match));
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i){
scanf("%s",s[i]+1);
}
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
if(s[i][j] == 'o'){
bel1[i][j] = ++cnt1;
while(s[i][j] == 'o' || s[i][j] == '*'){
bel1[i][j] = cnt1;
j++;
}
}
}
}
cnt2 = cnt1 + 1;
for(int j = 1;j <= m;++j){
for(int i = 1;i <= n;++i){
if(s[i][j] == 'o'){
bel2[i][j] = ++cnt2;
while(s[i][j] == 'o' || s[i][j] == '*'){
bel2[i][j] = cnt2;
i++;
}
}
}
}
int ans = 0;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
if(s[i][j] == 'o'){
Add(bel1[i][j],bel2[i][j]);
}
}
}
for(int i = 1;i <= cnt1;++i){
memset(vis,0,sizeof(vis));
ans += dfs(i);
}
printf("%d\n",ans);
return 0;
}
\(Never\ Give\ Up\)