总之就是 | ZROI NOIP联测 Day2
「0.0」序言
我是傻逼。
按照惯例还是简单描述题面,毕竟私题。虽然感觉描述的和原题面差不多了(
照例缺省源:
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#define Heriko return
#define Deltana 0
#define Romanno 1
#define S signed
#define LL long long
#define R register
#define I inline
#define CI const int
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false);cin.tie(0)
#define Files() freopen("RNMTQ.in","r",stdin);freopen("RNMTQ.out","w",stdout)
using namespace std;
template<typename J>
I void fr(J &x)
{
short f(1);x=0;char c=getchar();
while(c<'0' or c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while (c>='0' and c<='9')
{
x=(x<<3)+(x<<1)+(c^=48);
c=getchar();
}
x*=f;
}
template<typename J>
I void fw(J x,bool k)
{
if(x<0) x=-x,putchar('-');
static short stak[35];short top(0);
do
{
stak[top++]=x%10;
x/=10;
}
while(x);
while(top) putchar(stak[--top]+'0');
k?puts(""):putchar(' ');
}
「1.0」小游戏竟成阴影
有 \(2^n\) 个人玩剪刀石头布,且每个人出的手势固定。已知共有 \(r\) 个人会出石头,\(p\) 个人出布,\(s\) 个人出剪刀,保证 \(r+p+s=2^n\)。
比赛的顺序是 \(1\) 和 \(2\),\(3\) 和 \(4\)……
数据范围 \((n \le 15)\)。
「1.1」思路简述
实际上很显然能看出来整个的比赛流程是一颗满二叉树,因此最后的胜利者是根。
因为输赢关系是确定的,所以我们在确定了一个节点的根之后,就能确定整个二叉树,也就是说能得到一个对应的序列。
但是题目要求我们输出一个字典序最小的序列,所以我们在生成这个序列的时候需要贪心的按照 P R S
的顺序去生成。
考场写的 DFS
,但是很不幸写挂了,甚至写出了阴影,于是现在改为其它大佬的方程写法。简单来说就是设方程为 ans(dep,ri,pi,si)
,表示在第 dep
层的序列。
这里是直接用的 string
拼接,所以写起来会简单很多。
「1.2」Code
int t,n,r,p,s;
string ans[16][2][2][2];
I void Pre()
{
ans[0][1][0][0]="P",ans[0][0][1][0]="R",ans[0][0][0][1]="S";
for(int i(1);i<=15;++i)
if((1<<i)%3==1)
{
ans[i][0][1][0]=ans[i-1][1][1][0]+ans[i-1][0][1][1];
ans[i][1][0][0]=ans[i-1][1][1][0]+ans[i-1][1][0][1];
ans[i][0][0][1]=ans[i-1][1][0][1]+ans[i-1][0][1][1];
}
else if((1<<i)%3==2)
{
ans[i][0][1][1]=ans[i-1][0][1][0]+ans[i-1][0][0][1];
ans[i][1][1][0]=ans[i-1][1][0][0]+ans[i-1][0][1][0];
ans[i][1][0][1]=ans[i-1][1][0][0]+ans[i-1][0][0][1];
}
}
S main()
{
Pre();fr(t);
while(t--)
{
fr(r),fr(p),fr(s);n=r+p+s;
if(r-p>=2 or r-s>=2 or p-s>=2 or p-r>=2 or s-r>=2 or s-p>=2) {puts("-1");continue;}
short res(-1);int dep(n);
while(dep)
{
++res;
dep>>=1;
}
puts(ans[res][p-(n/3)][r-(n/3)][s-(n/3)].c_str());
}
Heriko Deltana;
}
「2.0」暴力分竟成全部
给出一个数字 \(a\),以及一个含 \(k\) 个数的数位集合 \(d_1 \cdots d_k\),求一个最小的无前缀 \(0\) 且不包含数位集合 \(d\) 中任意元素的 \(b = ax \ (x \in \Z^+)\)。
若无解输出 \(-1\),保证 \(b\) 的位数 \(\le 10^6,a \le 10^6,k \le 10\)。
「2.1」思路简述
这个题目涉及到了数位相关,这就很容易让人想到数位 DP,但是这个数据范围太大了,数位 DP 显然不是正解。
因为考场上写 A 写心态炸了,所以 B 就随便敲了个暴力。考场上没那么大胆只放了 \(b \le 10^5\) 拿 \(30\) pts,但是实际上暴力能跑 \(b \le 10^9\) 拿 \(50\) pts。然后就™只得了这 30 分
那么如果不用数位 DP 的话,我们就考虑如何去实现题目中的条件。题目中最重要的条件就是两个:倍数关系和不包含 \(d_i\) 这个数。
先考虑第一个,\(a\) 的倍数除了题面中的描述外,还能表现为 \(b \bmod a = 0\)。而满足这样条件的数可以从一个 \(\bmod a \ne 0\) 的数 \(x\) 变换最低位得到。
同时注意到,设数 \(t \bmod a = dlt\),那么在 \(t\) 后面加上一位 \(\beta\) 之后对 \(a\) 取模的结果显然是和 \((dlt \times 10 + \beta) \bmod a\) 等价,于是这样就之和模之后的余数有关而和 \(t\) 无关了。根据题意我们要找到最小的 \(b\),于是我们只需要 BFS 枚举填充下一位,不能出现的判掉即可,这样哦我们就解决了这两个问题。
「2.2」Code
CI MXX(2e6+1);
struct node
{
int val,from,dlt;
}
r[MXX];
void FW(int x) {if(!x) Heriko;FW(r[x].from);putchar(r[x].val+'0');}
bool vis[MXX],d[11];
S main()
{
queue<int> q;int a,k,cnt;
fr(a),fr(k);
for(int i(1),x;i<=k;++i) fr(x),d[x]=1;
r[0]=(node){0,-1,0};q.push(0);
while(q.size())
{
int x(q.front());q.pop();
for(int i(r[x].dlt?0:1);i<=9;++i)
{
if(!d[i])
{
int y(((r[x].dlt<<3)+(r[x].dlt<<1)+i)%a);
if(!vis[y])
{
vis[y]=1;r[++cnt]=(node){i,x,y};
if(!y)
{
FW(cnt);
Heriko Deltana;
}
q.push(cnt);
}
}
}
}
puts("-1");
Heriko Deltana;
}
「3.0」图论题竟成 DP
给定一个 \(2n\ (n \le 25)\) 个点的二分图,最初有 \(m\) 条边,要求添加尽可能少的边使得随意选取 \(n\) 条边形成的都是完全二分图。
「3.1」思路简述
如果要满足条件显然需要 \(n^2\) 条边,否则不能构成完全二分图。那么最差的情况下要连接 \(n^2-m\) 条边,但是整个过程实际上是连通块之间的合并。
于是不断去枚举连通块合并,也就是不断的枚举点形成连通块,再进一步合并直到满足要求。对于每个连通块我们维护其左右部分各有多少点,对于形态一样的连通块我们可以剪枝。
「3.2」Code
template<typename J>
I J Hmax(const J &x,const J &y) {Heriko x>y?x:y;}
template<typename J>
I J Hmin(const J &x,const J &y) {Heriko x<y?x:y;}
CI MXX(100005);
int t,n,fa[MXX],lsz[MXX],rsz[MXX],m,sum,ans;
bool vis[MXX];
vector< pair < int , int > > v;
char s[MXX];
int Find(int x)
{
if(fa[x]!=x) fa[x]=Find(fa[x]);
Heriko fa[x];
}
I void Uni(const int &x,const int &y)
{
int fx(Find(x)),fy(Find(y));
if(fx!=fy)
{
fa[fx]=fy;
lsz[fy]+=lsz[fx];rsz[fy]+=rsz[fx];
}
}
/*参数分别代表已经处理的联通块个数(k),选择的上一个要合成的联通块的位置(lst),
钦定要合并的连通块的左部点的个数之和(tot),
单个左部点个数(lt),单个右部点个数(rt),当前并入的联通块多出的右部点个数(dlt)*/
void DFS(int k,int lst,int tot,int lt,int rt,int dlt)
{
if(sum+Hmax(lt,rt)>=ans) Heriko;
if(!(~lst)) {if(k==v.size()) {ans=Hmin(ans,sum+lt);Heriko;}}
else
{
sum+=((tot+Hmax(0,dlt))*(tot+Hmax(0,dlt)));
if(dlt>0) {if(lt>=dlt) DFS(k,-1,0,lt-dlt,rt,0);}
else {if(rt>=-dlt) DFS(k,-1,0,lt,rt+dlt,0);}
sum-=((tot+Hmax(0,dlt))*(tot+Hmax(0,dlt)));
}
pair<int,int> now(make_pair(0,0));
for(int i(lst+1);i<(int)v.size();++i)
if(!vis[i] and v[i]!=now)/*剪枝*/
{
now=v[i];vis[i]=1;
DFS(k+1,i,tot+v[i].first,lt,rt,dlt-v[i].first+v[i].second);
vis[i]=0;
}
}
S main()
{
fr(t);
while(t--)
{
fr(n);int k(n<<1);
for(int i(1);i<=k;++i)
{
fa[i]=i;
lsz[i]=(i<=n);
rsz[i]=(i>n);
}
ans=n*n;sum=m=0;v.clear();
for(int i(1);i<=n;++i)
{
scanf("%s",s+1);
for(int j(1);j<=n;++j)
if(s[j]=='1')
++m,Uni(i,j+n);
}
int lt(0),rt(0);
for(int i(1);i<=k;++i)
if(fa[i]==i)
{
if(lsz[i]==rsz[i]) sum+=(lsz[i]*lsz[i]);
else if(!rsz[i]) ++lt;
else if(!lsz[i]) ++rt;
else v.push_back(make_pair(lsz[i],rsz[i]));
}
sort(v.begin(),v.end());
DFS(0,-1,0,lt,rt,0);fw(ans-m,1);
}
Heriko Deltana;
}
「4.0」图论题又成 DP
有一张有向图,有 \(2n^2\) 个,分成 \(n\) 组,每组有 \(2n\) 个点。
对于每一组内,对于所有 \(1≤i<n\) 都有 \(i→i+1,n+i→n+i+1\) 的边,对于所有 \(1≤i≤n\) 都有 \(i→n+i\) 的边。
然后对于所有 \(2≤i≤n\),第 \(1\) 组的 \(i+n−1\) 号点向第 \(i\) 组的 \(1\) 号,第 \(1\) 组的 \(i\) 号点向第 \(i\) 组的 \(n+1\) 号点都有连边。求这张图的拓扑序个数,答案很大,对读入的 \(mod\) 取模。
\(n=3\) 时图为:
「4.1」思路简述
虽然题目写的是图论成 DP,但是这种计数类问题显然是需要 DP 思想的。
发现如果选择点 \((1,i)\) 和 \((1,n+i-1)\) 之后(当然前提是这两个点能选),就有一些点独立出来了,也就是说其它点不会影响它的拓扑序,于是第一组点如何去选就不会影响到这一些了。
于是我们考虑对第一组做 DP。
设 \(f(i,j)\) 表示第一组第一行选了 \(i\) 个点,第二行选了 \(j\) 个点的方案数,\(g(i,j)\) 表示当前位置可行的排列数量。
更加详细的可以去 Dfkuaid 的笔记查看(
当然不是我懒,只是因为我太菜不会描述(
「4.2」Code
template<typename J>
I J Hmin(const J &x,const J &y) {Heriko x<y?x:y;}
CI NXX(3001),MXX(1.8e7+1);
int n,fac[MXX],inv[MXX],mod;
LL f[NXX][NXX],g[NXX][NXX];
int C(int a,int b)
{
if(a-b<mod and mod<=a) Heriko Deltana;
Heriko 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;
}
S main()
{
fr(n),fr(mod);int m((n*n)<<1);
fac[0]=inv[0]=inv[1]=1;
for(int i(1);i<=m;++i)
if(i==mod) fac[i]=fac[i-1];
else fac[i]=1ll*fac[i-1]*i%mod;
for(int i(2);i<=m;++i)
if(i>=mod) inv[i]=inv[i-mod];
else inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i(1);i<=m;++i) inv[i]=1ll*inv[i-1]*inv[i]%mod;
f[1][0]=1;g[n][n]=1;
for(int i(n);~i;--i)
for(int j(Hmin(n-1,i));~j;--j)
g[i][j]=(g[i+1][j]+g[i][j+1])%mod;
for(int i(1);i<=n;++i)
for(int j(0);j<i;++j)
{
int dlt((n<<1)*(n-j)-i-j);
if(j==i-1)
{
if(i==n) {fw(f[i][j],1);Heriko Deltana;}
(f[i+1][j+1]+=1ll*f[i][j]*C(dlt-2,n<<1)%mod*g[1][0]%mod)%=mod;
for(int k(1);k<=n;++k) (f[i+1][j+1]+=1ll*f[i][j]*C(dlt-k-2,(n<<1)-k)%mod*g[k][0]%mod)%=mod;
}
else (f[i][j+1]+=1ll*f[i][j]*C(dlt-1,n<<1)%mod*g[1][0]%mod)%=mod;
if(i<n) (f[i+1][j]+=f[i][j])%=mod;
}
Heriko Deltana;
}
「5.0」尾声
嗯最后成绩就那 \(30\) pts.