07.12-个人赛
A
题意
有一个非负整数 n ( n ≤ 1 0 9 ) n(n \le 10^{9}) n(n≤109)
给出它在二进制和三进制下的表示
二进制和三进制的表示中都恰好有一位有误
求出原来的 n n n (用十进制表示)
思路
枚举二进制下哪一位有误,然后修改那一位并假设它就是答案,再和题目给出的三进制进行比较
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
string st2,st3;
cin>>st2>>st3;
reverse(st2.begin(),st2.end());
reverse(st3.begin(),st3.end());
for (int i=0;i<st2.size();++i)
{
int res = 0;
for (int j=0;j<st2.size();++j)
{
if (i==j) res+=((st2[j]-'0')^1)<<j;
else res+=(st2[j]-'0')<<j;
}
vector <int> vc;
int n = res;
for (;res;)
{
vc.push_back(res%3);
res/=3;
}
if (vc.size()!=st3.size()) continue;
int diff = 0;
for (int j=0;j<vc.size();++j) diff+=(st3[j]!=(vc[j]+'0'));
if (diff==1)
{
cout<<n<<endl;
break;
}
}
return 0;
}
B
题意
给出 n n n 个数,问有多少个区间满足它的中位数 ≥ X \ge X ≥X
思路
中位数 ≥ X \ge X ≥X 这个条件可以近似地看成,至少一半的数都 ≥ X \ge X ≥X
将 ≥ X \ge X ≥X 的数视为 1 1 1 ,将 < X < X <X 的数视为 − 1 -1 −1
于是问题转化为有多少个区间满足它的和 ≥ 0 \ge 0 ≥0 (本题的中位数比较特殊)
这种静态的对于区间和的统计一般都可以转化为前缀和问题
比如对于区间 [ l , r ] [l,r] [l,r] 来说,它的和就是 s [ r ] − s [ l − 1 ] s[r]-s[l-1] s[r]−s[l−1]
统计有多少个和 ≥ 0 \ge 0 ≥0 的区间就是统计有多少对 [ l , r ] [l,r] [l,r] 满足 s [ r ] − s [ l − 1 ] ≥ 0 s[r]-s[l-1] \ge 0 s[r]−s[l−1]≥0 也就是 s [ l − 1 ] ≤ s [ r ] s[l-1] \le s[r] s[l−1]≤s[r]
等价于统计有多少对 ( i , j ) (i,j) (i,j) 满足 i < j i < j i<j 并且 s [ i ] < s [ j ] s[i] < s[j] s[i]<s[j] ,也就相当于求正序对了
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
inline int lowbit(const int x)
{
return x&-x;
}
int tr[N+N];
int a[N],s[N];
int n,X;
void add(int x)
{
for (int i=x;i<N+N;i+=lowbit(i)) ++tr[i];
}
int calc(int x)
{
int res=0;
for (int i=x;i;i^=lowbit(i)) res+=tr[i];
return res;
}
int main()
{
cin>>n>>X;
for (int i=1,x;i<=n;++i) scanf("%d",&x),a[i]=((x>=X) ? 1 : -1);
for (int i=1;i<=n;++i) s[i]=s[i-1]+a[i];
long long ans = 0;
add(0+N);
for (int i=1;i<=n;++i)
{
ans+=calc(s[i]+N);
add(s[i]+N);
}
cout<<ans<<'\n';
return 0;
}
C
题意
给出两个序列 A A A 和 B B B
问 A A A 中有多少个区间长得像 B B B
长的像是指满足以下条件
1.区间长度与 B B B 的长度相同
2.经过若干次重新排列以及整体加减后和 B B B 完全一致
思路
枚举区间,然后排个序,比较差值是否一致
#include <bits/stdc++.h>
using namespace std;
const int N = 1500005;
int a[N],b[N];
int n,m;
int ck(int pos)
{
int c[105];
for (int i=1;i<=m;++i) c[i]=a[pos+i-1];
sort(c+1,c+1+m);
for (int i=2;i<=m;++i) if (c[i] - c[1] != b[i] - b[1]) return 0;
return 1;
}
int main()
{
cin>>n;for (int i=1;i<=n;++i) scanf("%d",&a[i]);
cin>>m;for (int i=1;i<=m;++i) scanf("%d",&b[i]);
sort(b+1,b+1+m);
vector <int> ans;
for (int i=1;i+m-1<=n;++i)
{
if (ck(i)) ans.push_back(i);
}
cout<<ans.size()<<'\n';
for (auto it:ans) cout<<it<<'\n';
return 0;
}
D
题意
给出一个颜色序列
求出最小的区间使得它包含所有颜色
思路
暴力枚举就是先枚举左端点,然后右端点往右扫过去,直到所有颜色都包含
显然,对于更靠右的左端点,它对应的右端点要么不动,要么更靠右
所以就不用每次右端点都重新扫了,可以从上一次的右端点开始接着扫
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct pll { ll x,y; };
const int N = 1500005;
map <ll,int> mp;
pll a[N];
int n,totcnt;
int main()
{
cin>>n;
for (int i=1;i<=n;++i)
{
scanf("%lld%lld",&a[i].x,&a[i].y);
if (mp[a[i].y]==0) ++totcnt;
++mp[a[i].y];
}
mp.clear();
sort(a+1,a+1+n,[&](pll x,pll y)
{
return x.x<y.x;
}
);
long long ans = 1LL<<60;
int cnt = 0;
for (int l=1,r=0;l<=n && r<=n;)
{
while (cnt < totcnt && r<=n)
{
++r;
if (r>n) break;
if (mp[a[r].y]==0) ++cnt;
++mp[a[r].y];
}
if (r>n) break;
ans=min(ans,a[r].x-a[l].x);
--mp[a[l].y];
if (mp[a[l].y]==0) --cnt;
++l;
}
cout<<ans<<'\n';
return 0;
}
E
给出一张黑白图,上面有两个不相邻的黑色的联通块
问最少把几个白色格子染黑,来把这两个联通块连起来
求出两部分之间的最短路即可
#include <bits/stdc++.h>
using namespace std;
struct pii { int x,y; };
#define pb push_back
const int N = 1505;
const pii dir[4] = {{-1,0},{1,0},{0,-1},{0,1}};
pii operator + (pii x,pii y) {return {x.x+y.x,x.y+y.y};};
int a[N][N],dis[N][N];
int n,m;
void dfs(pii now)
{
if (a[now.x][now.y]==0) return;
if (dis[now.x][now.y] < 1e9) return;
dis[now.x][now.y]=0;a[now.x][now.y]=2;
for (int i=0;i<4;++i) dfs(now+dir[i]);
}
int main()
{
string st;
cin>>n>>m;
int stx,sty;
for (int i=1;i<=n;++i)
{
cin>>st;
for (int j=1;j<=m;++j)
{
a[i][j]=((st[j-1]=='X') ? 1 : 0);
if (a[i][j]) stx=i,sty=j;
dis[i][j] = 1e9;
}
}
dfs({stx,sty});
//for (int i=1;i<=n;++i) {for (int j=1;j<=m;++j) cout<<a[i][j]<<' ';cout<<endl;};
queue <pii> q;
for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) if (a[i][j]==2) q.push({i,j});
while (!q.empty())
{
pii now = q.front();q.pop();
for (int i=0;i<4;++i)
{
pii nw = now + dir[i];
if (nw.x<1 || nw.y<1 || nw.x>n || nw.y>m) continue;
if (dis[nw.x][nw.y]<1e9) continue;
dis[nw.x][nw.y]=dis[now.x][now.y]+1;
q.push(nw);
if (a[nw.x][nw.y]==1)
{
cout<<dis[nw.x][nw.y]-1<<'\n';
return 0;
}
}
}
return 0;
}
F
给出一个时间
求从11日11点11分到这个时间过了多久
若这个时间比11日11点11分要早,输出 − 1 -1 −1
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int D,H,M;
cin>>D>>H>>M;
int st = 11*24*60+11*60+11;
int ed = D *24*60+H *60+M ;
cout<<((st<=ed) ? (ed-st) : -1);
return 0;
}
G
题意
给出一个网格图,多次询问一个子矩形内联通块个数
思路
不会
H
题意
给出一张黑白图,上面有三个不相邻的黑色的联通块
问最少把几个白色格子染黑,来把这三个联通块连起来
思路
和E不一样的是,这题有三个联通块,一个显然的想法是枚举连接方式,然后转化为E题的做法
但是这不一定是最优的,比如下面这种情况
上面两种连接方式显然不如下面这种
于是就懒得多想了,直接上最小斯坦纳树板子
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct pii { int x,y; };
const pii dir[4] = {{-1,0},{1,0},{0,-1},{0,1}};
pii operator + (pii x,pii y) {return {x.x+y.x,x.y+y.y};};
struct pll {ll x,y;};
typedef vector <int> vi;
typedef vector <pll> vll;
#define pb push_back
const int N = 2605;
ll dp[10][N];
int inq[N],qcnt[N];
int spfa(vll * adj,ll * dp,const int n)
{
queue <int> q;
for (int i=1;i<=n;++i) inq[i]=1,q.push(i),qcnt[i]=0;
while (!q.empty())
{
int now=q.front();q.pop();inq[now]=0;++qcnt[now];
if (qcnt[now]>n) return -1;
for (auto nw:adj[now])
if (dp[now]+nw.y<dp[nw.x])
{
dp[nw.x]=dp[now]+nw.y;
if (!inq[nw.x])
{
inq[nw.x]=1;
q.push(nw.x);
}
}
}
return 0;
}
ll Min_Steiner_Tree(vll * adj,const vi & key,const int n)
{
int ALL = (1 << key.size()) - 1;
memset(dp,0x3f,sizeof(dp));
for (int i=1;i<=n;++i) dp[0][i]=0;
for (int i=0;i<key.size();++i) dp[1<<i][key[i]]=0;
for (int mask=1;mask<=ALL;++mask)
{
for (int subset=mask;subset>0;subset=(subset-1)&mask)
{
for (int i=1;i<=n;++i)
{
dp[mask][i]=min(dp[mask][i],dp[subset][i]+dp[mask^subset][i]);
}
}
int err=spfa(adj,dp[mask],n);
}
int mnid=1;
for (int i=1;i<=n;++i) if (dp[ALL][i]<dp[ALL][mnid]) mnid=i;
return dp[ALL][mnid];
}
vll adj[N];
int a[N][N],vis[N][N];
int n,m,k;
int row,column;
void dfs(pii now,int col)
{
if (a[now.x][now.y]>=0) return;
a[now.x][now.y]=col;
for (int i=0;i<4;++i) dfs(now+dir[i],col);
}
inline int getid(int i,int j)
{
return (i-1)*column+j;
}
int main()
{
string st;
cin>>row>>column;
for (int i=1;i<=row;++i)
{
cin>>st;
for (int j=1;j<=column;++j)
{
a[i][j]=((st[j-1]=='X') ? -1 : 0);
}
}
int cnt = 0;
for (int i=1;i<=row;++i)
{
for (int j=1;j<=column;++j)
if (a[i][j]==-1)
{
dfs({i,j},++cnt);
}
}
//for (int i=1;i<=row;++i) {for (int j=1;j<=column;++j) cout<<a[i][j]<<' ';cout<<endl;};
vector <int> key;
for (int id=1;id<=cnt;++id) key.pb(row*column+id);
for (int i=1;i<=row;++i)
for (int j=1;j<=column;++j)
{
if (a[i][j]>0) adj[getid(i,j)].pb({row*column+a[i][j],0}),adj[row*column+a[i][j]].pb({getid(i,j),0});
if (i>1) adj[getid(i,j)].pb({getid(i-1,j),1});
if (j>1) adj[getid(i,j)].pb({getid(i,j-1),1});
if (i<row) adj[getid(i,j)].pb({getid(i+1,j),1});
if (j<column) adj[getid(i,j)].pb({getid(i,j+1),1});
}
//for (auto it:key) cout<<it<<' ';cout<<'\n';
n = row * column + cnt;
ll ans=Min_Steiner_Tree(adj,key,n);
cout<<ans-cnt+1<<endl;
return 0;
}
课后练习:最小斯坦纳树
I
题意
给出 n n n 条线段
最多能选出几条线段使它们没有交点
思路
把竖直的线段放左边,水平的线段放右边
把相交的线段之间连起来,发现这是一张二分图
我们要求的就是它的最大独立集
在二分图上,满足 最大独立集=点数-最大匹配数
直接跑一个二分图匹配
如下图所示,判断线段是否相交也是比较简单的
PS:这个板子年代有点久远了,不建议直接抄
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
const int N = 1505;
int n,m;
struct Directed_Graph
{
int v_n,e_n;
int head[1005];
struct Edge
{
int st,ed,nxt;
}e[250005];
void init()
{
v_n=e_n=0;
memset(head,0,sizeof(head));
}
void addedge(int u,int v)
{
++e_n;
e[e_n].st=u;
e[e_n].ed=v;
e[e_n].nxt=head[u];
head[u]=e_n;
}
int flag[1005],match[1005];
int augment(int x)
{
if (x==0) return 1;
for (int i=head[x];i;i=e[i].nxt)
if (!flag[e[i].ed])
{
int y=e[i].ed;
flag[y]=1;
if (!augment(match[y])) continue;
match[y]=x;
return 1;
}
return 0;
}
int getmatch()
{
memset(match,0,sizeof(match));
int match_tot=0;
for (int i=1;i<=n;++i)
{
memset(flag,0,sizeof(flag));
match_tot+=augment(i);
}
return match_tot;
}
}g1;
struct Node
{
int u1,v1,u2,v2;
}a[N],b[N];
int ck(const Node & x,const Node & y)
{
if (y.u1<=x.u1 && x.u1<=y.u2)
{
if (x.v1<=y.v1 && y.v1<=x.v2)
return 1;
}
return 0;
}
int main()
{
vector <Node> vca,vcb;
int n1;cin>>n1;
for (int i=1;i<=n1;++i)
{
int u1,v1,u2,v2;
cin>>u1>>v1>>u2>>v2;
if (u1==u2)
{
if (v1>v2) swap(v1,v2);
vca.pb(a[++n]={u1,v1,u2,v2});
}else
{
if (u1>u2) swap(u1,u2);
vcb.pb(b[++m]={u1,v1,u2,v2});
}
}
int ans = max(vca.size(),vcb.size());
for (int i=1;i<=n;++i)
{
for (int j=1;j<=m;++j)
{
if (ck(a[i],b[j]))
{
g1.addedge(i,n+j);
}
}
}
g1.v_n=n+m;
int res = g1.getmatch();
ans=max(ans,n+m-res);
cout<<ans<<endl;
return 0;
}
J
K
题意
给出 n n n 个数,对它们进行修改使它们的平方和恰好为 m m m
每个数只能修改一次,修改的代价为 ( 修 改 后 的 数 − 修 改 前 的 数 ) 2 (修改后的数-修改前的数)^{2} (修改后的数−修改前的数)2
求最小花费
思路
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个数,平方和为 j j j 的最小花费
枚举下一个数修改后的值进行转移
时间复杂度 O ( N ∗ M ∗ M A X ( A i ) ) O(N*M*MAX(A_{i})) O(N∗M∗MAX(Ai))
#include <bits/stdc++.h>
using namespace std;
const int N = 1500005;
int a[15];
int dp[11][10005];
int n,m;
int main()
{
cin>>n>>m;
for (int i=1;i<=n;++i) scanf("%d",&a[i]);
sort(a+1,a+1+n);
memset(dp,60,sizeof(dp));
//cout<<"inf="<<dp[0][0]<<endl;
dp[0][0]=0;
for (int i=0;i<n;++i)
for (int j=0;j<m;++j)
if (dp[i][j]<1e9)
{
for (int nw=0;nw*nw<=m-j;++nw)
{
dp[i+1][j+nw*nw]=min(dp[i+1][j+nw*nw],dp[i][j]+(a[i+1]-nw)*(a[i+1]-nw));
}
}
cout<<((dp[n][m]>=1e9) ? -1 : dp[n][m])<<'\n';
return 0;
}
L
题意
给出一个 9 ∗ 9 9*9 9∗9 的 0 / 1 0/1 0/1 网格
对它进行修改,使它每行,每列,每个 3 ∗ 3 3*3 3∗3 都恰好有偶数个 1 1 1
求最少修改次数
思路
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示前 i i i 行, 每一列的奇偶性的状压值是 j j j ,当前的三个 3 ∗ 3 3*3 3∗3 的奇偶性的状压值是 k k k 的最小修改次数
枚举下一行的情况进行转移
时间复杂度 O ( 9 ∗ 2 9 ∗ 2 3 ∗ 2 9 ) = O ( 9 ∗ 2 21 ) O(9*2^{9}*2^{3}*2^{9})=O(9*2^{21}) O(9∗29∗23∗29)=O(9∗221)
#include <bits/stdc++.h>
using namespace std;
int dp[10][513][9];
int a[15],c[1005][4];
int n = 9;
int main()
{
string st;
for (int i=1;i<=n;++i)
{
cin>>st;
for (int j=0;j<n;++j) a[i]=a[i]|((st[j]-'0')<<j);
}
for (int i=0;i<(1<<n);++i)
{
int cnt = 0;
for (int j=0;j<n;++j) cnt+=((i>>j)&1);
for (int j=0;j<3;++j) c[i][1]+=((i>>j)&1);
for (int j=3;j<6;++j) c[i][2]+=((i>>j)&1);
for (int j=6;j<9;++j) c[i][3]+=((i>>j)&1);
c[i][0]=c[i][1]+c[i][2]+c[i][3];
}
memset(dp,60,sizeof(dp));
dp[0][0][0]=0;
for (int i=0;i<n;++i)
{
int ni = i + 1,nj,nk;
for (int j=0;j<(1<<n);++j)
{
for (int k=0;k<(1<<3);++k)
{
//cout<<"now="<<i<<' '<<j<<' '<<k<<endl;
for (int nw = 0;nw < (1<<n);++nw)
if (!(c[nw][0]&1))
{
//cout<<"nw="<<nw<<endl;
nj=j^nw;
if ((i+1)%3>0)
{
if (i%3==0) nk = (c[nw][1]&1) | ((c[nw][2]&1)<<1) | ((c[nw][3]&1)<<2);
else nk = k ^ ((c[nw][1]&1) | ((c[nw][2]&1)<<1) | ((c[nw][3]&1)<<2));
if (i==1 && j==0 && k==0 && nw==0)
{
//cout<<"trans="<<ni<<' '<<nj<<' '<<nk<<endl;
}
dp[ni][nj][nk]=min(dp[i][j][k]+c[nw^a[ni]][0],dp[ni][nj][nk]);
}else
{
if (((k>>0)&1)^(c[nw][1]&1)) continue;
if (((k>>1)&1)^(c[nw][2]&1)) continue;
if (((k>>2)&1)^(c[nw][3]&1)) continue;
nk=0;
dp[ni][nj][nk]=min(dp[i][j][k]+c[nw^a[ni]][0],dp[ni][nj][nk]);
}
}
}
}
}
cout<<dp[n][0][0]<<endl;
return 0;
}