【题解】电子科技大学第十九届 ACM 程序设计竞赛 - 初赛
第十四名 —— B站关注嘉然
A
动态规划
\(presum[i]\) 为原序列位置 \(i\) 之前 \(0\) 减 \(1\) 个数的差值
\(f[i]\) 为最后一次在位置 \(i\) 操作,位置 \(i\) 之前 \(0\) 减 \(1\) 个数的最大差值
\(f[i]=max\{f[j]-presum[j]+presum[i-3]\}\),其中 \(j<=i-3\)
\(d[i]\) 为最后一次在位置 \(i\) 操作,整个序列中 \(0\) 减 \(1\) 个数的最大差值
\(d[i]=f[i]+presum[n]-presum[i]\)
考虑所有最后一次操作可能位置的差值 \(d[i]\),以及一次也没有进行操作的差值 \(presum[n]\)
答案即为 \(max\{max\{d[i]\},presum[n]\}\)
#include <cstdio>
#include <iostream>
using namespace std;
int n,m[100001],f[100001],presum[100001],premax[100001],ans;
int solve(){
int ret=0;
for (int i=1;i<=n;i++) presum[i]=presum[i-1]+(m[i]==0?1:-1);
for (int i=3;i<=n;i++){
f[i]=premax[i-3]+presum[i-3];
premax[i]=max(premax[i-1],f[i]-presum[i]);
}
for (int i=3;i<=n;i++){
f[i]+=presum[n]-presum[i];
ret=max(ret,f[i]);
}
ret=max(ret,presum[n]);
return ret;
}
int main(){
scanf("%d\n",&n);
for (int i=1;i<=n;i++){
scanf("%c",&m[i]);
m[i]-='0';
}
ans=solve();
for (int i=1;i<=n;i++) m[i]=!m[i];
ans=max(ans,solve());
printf("%d",ans);
}
B
模拟
检测两个集合的关系,统计对应元素是否出现即可
#include <cstdio>
#include <set>
using namespace std;
int a[100001],b[100001];
int p1=0,p2=0,p3=0;
set<int> sa,sb;
int main(){
int n,m;
scanf("%d",&n);
for (int i=0;i<n;i++){
scanf("%d",&a[i]);
sa.insert(a[i]);
}
scanf("%d",&m);
for (int i=0;i<m;i++){
scanf("%d",&b[i]);
sb.insert(b[i]);
}
for (int i=0;i<n;i++){
if (sb.count(a[i])==0)
p1=1;
}
for (int i=0;i<m;i++){
if (sa.count(b[i])==0)
p2=1;
if (sa.count(b[i])==1)
p3=1;
}
if (p1==0&&p2==0){
printf("A equals B");
}else{
if (p1==1&&p2==0){
printf("B is a proper subset of A");
}else{
if (p1==0&&p2==1){
printf("A is a proper subset of B");
}else{
if (p3==0)
printf("A and B are disjoint");
else
printf("I am confused!");
}
}
}
}
C
数论
用最小质因子 \(2\) 做桥梁即可将所有数联系起来,所以答案为 \(max\{2*(n以内的最大质数),n\}\)
#include <cstdio>
typedef long long ll;
const ll M=1000001;
ll t,n,pri[M],a,b;
ll ans[M];
int main(){
for (ll i=2;i<=M;i++) pri[i]=1;
for (ll i=2;i<=M;i++){
if (pri[i]){
for (ll j=i*i;j<=M;j+=i)
pri[j]=0;
}
}
b=2;
for (ll i=3;i<=M;i++){
if (pri[i]){
a=i;
}
ans[i]=a*2;
}
scanf("%lld",&t);
for (ll i=0;i<t;i++){
scanf("%lld",&n);
printf("%lld\n",ans[n]>n?ans[n]:n);
}
}
D
字符串哈希
对字符串做哈希后用 map 统计出现次数即可
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#define ll long long
using namespace std;
struct P{
int c1,c2;
friend bool operator< (const P &a,const P &b){
return a.c1!=b.c1?a.c1<b.c1:a.c2>b.c2;
}
}p[500001];
int cnt;
map<ll,int> s;
priority_queue<P> q;
ll calchash(char *s){
int len=strlen(s);
ll sum=0;
for (int i=0;i<len;i++)
sum+=131*sum+s[i];
return sum;
}
char s1[500001][51],s2[500001][51];
ll shash[500001];
int main(){
while(~scanf("%s %s",s1[cnt],s2[cnt])){
shash[cnt]=calchash(s1[cnt]);
if(s.count(shash[cnt])==0)
s[shash[cnt]]=0;
else
s[shash[cnt]]++;
p[cnt].c2=cnt;
cnt++;
}
for (int i=0;i<cnt;i++){
p[i].c1=s[shash[i]];
q.push(p[i]);
}
while(!q.empty()){
cout<<s1[q.top().c2]<<" "<<s2[q.top().c2]<<endl;
q.pop();
}
}
E
离散化
用优先队列做离散化之后模拟即可
#include <cstdio>
#include <queue>
#include <iostream>
using namespace std;
typedef pair<int,int> P;
const int N=200001;
priority_queue<P,vector<P>,greater<P> > q1;
priority_queue<int,vector<int>,greater<int> > q2;
int n,s,t,x=1,ans=0;
int main(){
scanf("%d",&n);
for (int i=0;i<n;i++){
scanf("%d%d",&s,&t);
q1.push(P(s,t));
}
while (!q1.empty()||!q2.empty()){
if (q2.empty()) x=q1.top().first;
while (!q1.empty()&&q1.top().first==x){
P p=q1.top();q1.pop();
q2.push(p.second);
}
while (!q2.empty()&&q2.top()<x) q2.pop();
if (q2.top()>=x) {
q2.pop();
ans++;
x++;
}
}
printf("%d",ans);
}
F
广度优先搜索
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
struct P{
int x,y,len;
P(int ax,int ay,int alen){
x=ax,y=ay,len=alen;
}
};
int n,x,y,a,b,ans,map[1003][1003];
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
queue<P> q;
int main(){
scanf("%d%d%d",&x,&y,&n);
x+=501,y+=501;
for (int i=0;i<n;i++){
scanf("%d%d",&a,&b);
a+=501,b+=501;
map[a][b]=1;
}
q.push(P(501,501,0));
while (!q.empty()){
P k=q.front();
if (k.x==x&&k.y==y) ans=k.len;
for (int i=0;i<4;i++){
int ax=k.x+dir[i][0],ay=k.y+dir[i][1];
if (0<=ax&&ax<1003&&0<=ay&&ay<1003&&map[ax][ay]==0){
map[ax][ay]=1;
q.push(P(ax,ay,k.len+1));
}
}
q.pop();
}
printf("%d",ans);
}
J
贪心
去掉绝对值符号后,比较大的 \(n/2\) 个数符号取正,比较小的 \(n/2\) 个数符号取负,靠中间的两个数特殊处理一下即可
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,h[100001],ans,ans2;
int main(){
scanf("%lld",&n);
for (ll i=0;i<n;i++) scanf("%lld",&h[i]);
sort(h,h+n);
if (n%2==0){
for (ll i=0;i<n/2;i++) ans-=h[i]*2;
for (ll i=n/2;i<n;i++) ans+=h[i]*2;
ans+=h[n/2-1]-h[n/2];
}else{
for (ll i=0;i<n/2;i++) ans-=h[i]*2;
for (ll i=n/2;i<n;i++) ans+=h[i]*2;
ans+=-h[n/2]-h[n/2+1];
for (ll i=0;i<n/2+1;i++) ans2-=h[i]*2;
for (ll i=n/2+1;i<n;i++) ans2+=h[i]*2;
ans2+=h[n/2]+h[n/2-1];
ans=max(ans,ans2);
}
printf("%lld",ans);
}
K
图论
维护每个节点 \(x\) 的子树大小 \(siz[x]\),所有子节点到节点 \(x\) 的路径和 \(len[x]\)
节点 \(1\) 连接到节点 \(i\) 时,形成一个基环外向树,设节点 \(x\) 的外向树大小为 \(subsiz[x]\)
基环上节点 \(i\) 的外向树大小为 \(subsiz[i]=siz[i]\),基环上其他节点 \(j\) 的外向树大小为 \(subsiz[j]=siz[j]-siz[son[j]]\),其中 \(son[j]\) 为基环上节点 \(j\) 的子节点
考虑基环上每个节点 \(x\) 的外向树对答案的贡献为 \(subsiz[x]*cirlen[x]+len[x]\),其中 \(cirlen[x]\) 为节点 \(x\) 通过基环到达节点 \(1\) 的最短路径长度
随着基环长度变化,基环上部分节点的 \(cirlen[x]\) 也会变化,但是考虑到会变化的区间是连续靠下方的半段,所以可以维护这一部分变化区间的 \(subsiz[x]\) 的和 \(downsum\)
每次更新直接对答案加上对应的区间和 \(downsum\) 即可用 \(O(1)\) 的复杂度维护答案中的 \(subsiz[x]*cirlen[x]\)
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=1e5+1;
int n,u,v,siz[N],len[N],idxsta[N],dep,substa[N],downsum,ans,fans=1e9;
int sz,head[N],vis[N];
struct E{
int next,to;
}e[N*2];
void insert(int a,int b){
sz++;
e[sz].next=head[a];
head[a]=sz;
e[sz].to=b;
}
void dfs(int x){
vis[x]=1;
siz[x]=1;
len[x]=0;
for (int i=head[x];i;i=e[i].next){
int v=e[i].to;
if (!vis[v]){
dfs(v);
siz[x]+=siz[v];
len[x]+=len[v]+siz[v];
}
}
}
void solve(int x){
dep++;
vis[x]=1;
idxsta[dep]=x;
if (dep%2==1) downsum-=substa[dep/2+1];
downsum+=siz[x];
ans+=len[x];
ans+=downsum;
fans=min(ans,fans);
ans-=downsum;
ans-=len[x];
downsum-=siz[x];
if (dep%2==1) downsum+=substa[dep/2+1];
for (int i=head[x];i;i=e[i].next){
int v=e[i].to;
if (!vis[v]){
substa[dep]=siz[x]-siz[v];
if (dep%2==1) downsum-=substa[dep/2+1];
downsum+=substa[dep];
ans+=len[x]-len[v]-siz[v];
ans+=downsum;
solve(v);
ans-=downsum;
ans-=len[x]-len[v]-siz[v];
downsum-=substa[dep];
if (dep%2==1) downsum+=substa[dep/2+1];
}
}
dep--;
}
int main(){
scanf("%d",&n);
for (int i=1;i<n;i++){
scanf("%d%d",&u,&v);
insert(u,v);
insert(v,u);
}
dfs(1);
memset(vis,0,sizeof(vis));
solve(1);
printf("%d",fans);
}
L
回文串
Manacher 结合前缀和,统计每种长度对应的字符串个数即可
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll M=100001,K=1e9+7;
ll n,k,d[M],mul[M],cnt[M],sum=0,ans=1;
char s[M];
ll binpow(ll a, ll b, ll m) {
a %= m;
ll res = 1;
while (b > 0) {
if (b & 1) res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
int main(){
scanf("%lld%lld\n",&n,&k);
scanf("%s",s);
for (ll i = 0, l = 0, r = -1; i < n; i++) {
ll k = (i > r) ? 1 : min(d[l + r - i], r - i);
while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
k++;
}
d[i] = k--;
if (i + k > r) {
l = i - k;
r = i + k;
}
}
for (ll i=0;i<n;i++){
d[i]=d[i]*2-1;
cnt[d[i]]++;
}
for (ll i=n/2*2+1;i>=1;i-=2){
sum+=cnt[i];
if (k>sum){
ans=ans*binpow(i,sum,K)%K;
k-=sum;
}else{
ans=ans*binpow(i,k,K)%K;
k=0;
}
}
if (k>0) ans=-1;
printf("%lld",ans);
}
N
贪心
选最小的 \(k/2\) 个坐标和最大的 \(k/2\) 个坐标即可
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,k,x[100001],ans;
int main(){
scanf("%lld%lld",&n,&k);
for (ll i=0;i<n;i++) scanf("%lld",&x[i]);
sort(x,x+n);
for (ll i=0;i<k/2;i++)
ans-=(k-1-i*2)*x[i];
for (ll i=0;i<k/2;i++)
ans+=(k-1-i*2)*x[n-1-i];
printf("%lld",ans);
}