初三奥赛模拟测试3
初三奥赛模拟测试3
\(145pts\),没挂分
T1 网格图 \(100pts\)
\(1\leq k \leq n \leq 500\)
水题,考虑枚举使用魔法的方块位置,这时候在方块里dfs就能拿到\(50pts\)的高分
考虑预处理出来每个连通块,之后滑动窗口处理,每次统计一列的状态,复杂度\(O(n^2 k)\)
CODE
#include<bits/stdc++.h>
using namespace std;
long long n,k,siz,ans[260010],bl[510][510],last,jud[260010],cx,cc;
char s[510][510];
bool vis[510][510];
void dfs(int x,int y,int num);
void spread(int x,int y,int num)
{
if(vis[x][y]==0&&s[x][y]=='.')
dfs(x,y,num);
}
void dfs(int x,int y,int num)
{
vis[x][y]=1;
bl[x][y]=num;
ans[num]++;
spread(x+1,y,num),spread(x,y+1,num),spread(x-1,y,num),spread(x,y-1,num);
}
void in(int a,int b,int c)
{
for(int i=b;i<=c;i++)
{
if(s[a][i]=='X') cx++;
if(jud[bl[a+1][i]]==0)
cc+=ans[bl[a+1][i]];
jud[bl[a+1][i]]++;
}
if(jud[bl[a][b-1]]==0)
cc+=ans[bl[a][b-1]];
jud[bl[a][b-1]]++;
if(jud[bl[a][c+1]]==0)
cc+=ans[bl[a][c+1]];
jud[bl[a][c+1]]++;
}
void out(int a,int b,int c)
{
for(int i=b;i<=c;i++)
{
if(s[a][i]=='X') cx--;
jud[bl[a-1][i]]--;
if(jud[bl[a-1][i]]==0)
cc-=ans[bl[a-1][i]];
}
jud[bl[a][b-1]]--;
if(jud[bl[a][b-1]]==0)
cc-=ans[bl[a][b-1]];
jud[bl[a][c+1]]--;
if(jud[bl[a][c+1]]==0)
cc-=ans[bl[a][c+1]];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(vis[i][j]==0&&s[i][j]=='.')
siz++,dfs(i,j,siz);
for(int i=1;i<=n-k+1;i++)
{
memset(jud,0,siz*8+80);
cc=cx=0;
for(int j=1;j<=k;j++)
in(j,i,i+k-1);
for(int p=i;p<=i+k-1;p++)
{
if(jud[bl[1][p]]==0)
cc+=ans[bl[1][p]];
jud[bl[1][p]]++;
}
last=max(cc+cx,last);
for(int j=2;j<=n-k+1;j++)
{
out(j-1,i,i+k-1);
in(j+k-1,i,i+k-1);
last=max(cc+cx,last);
}
}
printf("%lld\n",last);
return 0;
}
T2 序列问题 $ 45pts $
考虑dp,暴力柿子是这样的(考场做法)
\[dp_{i}=\max_{j < i ,i-a_{i} \leq j-a_{j},dp_{j} < a_{j}} dp_{j}+1
\]
枚举每个\(j\)转移即可获得$ 45pts $高分
考虑优化,我们发现\(i-a_{i} \leq j-a_{j}\)可以写成\(i-j\leq a_{i} -a_{j}\),又有\(dp_{j} < a_{j}\),可得 \(j<i\) 所以我们不需要考虑条件一了,首先按\(a_{i}\)的值排序,用权值树状数组维护前缀最大值即可
CODE
#include<bits/stdc++.h>
using namespace std;
long long n,dp[500100],last,t[500100],now;
struct NODE
{
long long dis,a;
const friend bool operator <(const NODE p,const NODE q){return p.a<q.a;}
}b[500100];
void in(int a,long long s)
{
for(int i=a+1;i<=n+1;i+=(i&(-i)))
t[i]=max(t[i],s);
}
long long find(int a)
{
long long ans=0;
for(int i=a+1;i>=1;i-=(i&(-i)))
ans=max(t[i],ans);
return ans;
}
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&b[i].a);
b[i].dis=i-b[i].a;
}
sort(b+1,b+n+1);
now=1;
for(int i=1;i<=n;i++)
{
if(b[i].a!=b[i-1].a)
while(now<i)
{
if(0<=b[now].dis&&b[now].dis<=n)
in(b[now].dis,dp[now]);
now++;
}
if(b[i].dis<0||b[i].a>n)
continue;
dp[i]=find(b[i].dis)+1;
last=max(dp[i],last);
}
printf("%lld\n",last);
return 0;
}
T3 置换
不会
T4 同桌的你
典中典之基环树\(dp\)
考虑根据喜欢关系建边,之后明显\(n\)个节点\(n\)条边,形成了一个外向树森林
我们发现,对于环上的每个点,最多只有一条和它相连的边产生贡献,因此对于每棵基环树只要在一个环上点分别尝试断和它相连的两条边之后取最优即可,建反边,设 $ dp_{i,0} $ 表示这个节点不与其儿子做同桌时子树内最大贡献,$ dp_{i,1} $ 表示这个节点与其儿子做同桌时子树内最大贡献
有转移方程
\[dp_{i,0} = \sum_{j\in son(i)} \max\{dp_{j,0},dp_{j,1}\}
\]
\[dp_{i,1} = dp_{i,0}- \min_{j\in son(i)} \{dp_{j,0}-\max\{dp_{j,0},dp_{j,1}\}+1
\]
至于男女,开\(pair\),作为第二元素直接转移即可,在第二个中如果 $ i \ne j$ 就把它加一,细节可以看代码。路径就记录第二个式子里取到最优的j即可
CODE
#include<bits/stdc++.h>
using namespace std;
long long n,t,lk[1001000],is[1001000],son[1001000],nxt[1001000],root[2],pre[1001000][2];
bool vis[1001000][2],sol[1001000],huan[1001000];
pair<int,bool> us;
queue <pair<int,bool>> rec;
struct NODE
{
long long lk,sex;
const friend NODE operator + (NODE q,NODE p)
{
NODE ans;
ans.sex=p.sex+q.sex;
ans.lk=p.lk+q.lk;
return ans;
}
const friend NODE operator - (NODE q,NODE p)
{
NODE ans;
ans.sex=q.sex-p.sex;
ans.lk=q.lk-p.lk;
return ans;
}
const friend bool operator < (NODE q,NODE p)
{
if(p.lk==q.lk)
return q.sex<p.sex;
return q.lk<p.lk;
}
}dp[1001000][2][2],anss,last;
NODE max(NODE q,NODE p)
{
if(q<p) return p;
return q;
}
long long find(int fr)
{
long long g;
if(huan[fr]==0){huan[fr]=1;g=find(lk[fr]),huan[fr]=0;return g;}
huan[fr]=0;
return fr;
}
void dfs(int now,bool op)
{
vis[now][op]=1;
for(int i=son[now];i;i=nxt[i])
{
if(i==root[op]) continue;
dfs(i,op);
dp[now][0][op]=dp[now][0][op]+max(dp[i][0][op],dp[i][1][op]);
}
NODE mid={(long long)1e18,(long long)1e18},add={1,0};
for(int i=son[now];i;i=nxt[i])
{
if(i==root[op]) continue;
add={1,0};
if(is[i]!=is[now])
add={1,1};
if(max(dp[i][0][op],dp[i][1][op])-dp[i][0][op]-add<mid)
mid=max(dp[i][0][op],dp[i][1][op])-dp[i][0][op]-add,pre[now][op]=i;
}
dp[now][1][op]=dp[now][0][op]-mid;
}
void solve(int now)
{
anss={0,0};
root[0]=find(now);
vis[root[0]][0]=1;
dfs(root[0],0);
root[1]=lk[root[0]];
vis[root[1]][1]=1;
dfs(root[1],1);
if(anss<dp[root[0]][0][0])
anss=dp[root[0]][0][0],us.first=root[0],us.second=0;
if(anss<dp[root[0]][1][0])
anss=dp[root[0]][1][0],us.first=root[0],us.second=0;
if(anss<dp[root[1]][0][1])
anss=dp[root[1]][0][1],us.first=root[1],us.second=1;
if(anss<dp[root[1]][1][1])
anss=dp[root[1]][1][1],us.first=root[1],us.second=1;
last=anss+last;
rec.push(us);
}
void output(int now,bool op)
{
if(sol[now]==0)
if(dp[now][0][op]<dp[now][1][op])
printf("%d %lld\n",now,pre[now][op]),sol[pre[now][op]]=sol[now]=1;
for(int i=son[now];i;i=nxt[i])
{
if(i==root[op]) continue;
output(i,op);
}
}
void add(int a,int b){nxt[a]=son[b];son[b]=a;}
int main()
{
freopen("deskmate.in","r",stdin);
freopen("deskmate.out","w",stdout);
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&lk[i],&is[i]),add(i,lk[i]);
for(int i=1;i<=n;i++)
if(vis[i][0]==0)
solve(i);
printf("%lld %lld\n",last.lk,last.sex);
while(!rec.empty())
{
root[rec.front().second]=rec.front().first;
output(rec.front().first,rec.front().second);
rec.pop();
}
memset(dp,0,sizeof(dp));
memset(son,0,sizeof(son));
memset(nxt,0,sizeof(nxt));
memset(pre,0,sizeof(pre));
memset(vis,0,sizeof(vis));
memset(sol,0,sizeof(sol));
last={0,0};
}
return 0;
}