NOIP 模拟15
20+25+0+100 寄了,前仨题每道题都只会了一半也是挺厉害的。
A.数字变换
每次操作后 \((a+b)\bmod p\) 的值不变,所以可以先判断 \((a+b)\bmod p\) 是否等于 \((c+d)\bmod p\),不等的话一定无解。
然后就只需要考虑 \(a\),当 \(a=c\) 时就找到了答案。每次可以将 \(a\gets 2a\bmod p\) 或者 \(a\gets (a-b)\bmod p\),后者相当于 \((2*a-(a+b))\bmod p\)。
所以相当于操作 \(i\) 次后,\(a\) 可以变为 \((2^ia-x(a-b))\bmod p,x\in[0,2^i-1]\),因为你在第 \(j\) 次选择操作二最后会减掉 \(2^{i-j}\) 次 \(k\),所以相当于每个二进制位都可以分开考虑,所以 \(x\in[0,2^i-1]\)。
使 \(2^ia-x(a-b)\equiv c\pmod p\) 即 \(x(a-b+p)\equiv 2^ia-c\pmod p\),枚举 \(i\) 求解不定方程,当最小正整数 \(x<2^i\) 时 \(i\) 就是合法解,当 \(p\leq 2^i\) 次方时一定有解,复杂度 \(O(q\log p)\)。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<utility>
#include<queue>
#define int long long
using namespace std;
int p,T;
typedef pair<int,int> P;
unordered_map <int,int> h;
int exgcd(int a,int b,int &x,int &y)
{
if(!b){x=1,y=0;return a;}
int d=exgcd(b,a%b,y,x);
y-=(a/b)*x;return d;
}
main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>p>>T;
while(T--)
{
int a,b,c,d,k;cin>>a>>b>>c>>d;k=(a+b)%p;
if((c+d)%p!=(a+b)%p){cout<<"-1\n";continue;}
if(a==c){cout<<"0\n";continue;}
for(int i=1;;++i)
{
a=a*2%p;int now=(a-c+p)%p;
int x,y;int d=exgcd(k,p,x,y);
if(now%d) continue;x=(x*now%p+p)%p;
if(x<(1<<i)){cout<<i<<'\n';break;}
}
}
return 0;
}
B.均分财产
数据随机,所以直接前 \(n-25\) 个数依次考虑,当 \(sum\leq 0\) 时放到第一个集合中,使 \(sum\gets sum+a_i\),\(sum>0\) 反之。这样维护出来差值。
然后枚举后 \(25\) 个数放到哪个集合中,最后使差值变为 \(0\),由于数据随机所以大概率是可以的,但是我跑的时候需要 random_shuffle
。找到解直接跳出,卡时输出 -1
。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<time.h>
using namespace std;
const int MAXN=2e5+10;
int n,k,a[MAXN],c[MAXN],pos[MAXN];long long now;
void dfs(int x,long long sum,int k)
{
if(clock()*1.0/CLOCKS_PER_SEC>0.98) cout<<"-1\n",exit(0);
if(x>min(n,25))
{
if(!sum)
{
int cnta=0,cntb=0;
for(int i=1;i<=n;++i)
{
if(c[i]==1) ++cnta;
if(c[i]==2) ++cntb;
}
cout<<cnta<<' ';for(int i=1;i<=n;++i) if(c[i]==1) cout<<pos[i]<<' ';cout<<'\n';
cout<<cntb<<' ';for(int i=1;i<=n;++i) if(c[i]==2) cout<<pos[i]<<' ';cout<<'\n';
exit(0);
}
return ;
}
if(k) c[x]=0,dfs(x+1,sum,k-1);
c[x]=1,dfs(x+1,sum+a[pos[x]],k);
c[x]=2,dfs(x+1,sum-a[pos[x]],k);
return ;
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>k;for(int i=1;i<=n;++i) cin>>a[i],pos[i]=i;
srand(time(0)),rand();random_shuffle(pos+1,pos+1+n);
if(n<=25) dfs(1,0,k),cout<<"-1\n",exit(0);
for(int i=26;i<=n;++i)
{
if(now<=0) c[i]=1,now+=a[pos[i]];
else c[i]=2,now-=a[pos[i]];
}
dfs(1,now,25),cout<<"-1\n",exit(0);
}
C.查询工资
感觉情况的考虑很复杂,自己不会比题解说的清楚了,不写了。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN=8e5+10;
int T,n,k,fa[MAXN],siz[MAXN],f[MAXN];
vector <int> v[MAXN];
inline void clear()
{
for(int i=1;i<=n;++i) v[i].clear();
return ;
}
void dfs(int x,int fa=0)
{
siz[x]=1,f[x]=0;
int now=0;bool flag=false;
for(int y:v[x])
{
if(y==fa) continue;
dfs(y,x);siz[x]+=siz[y],f[x]+=f[y];
if(siz[y]>=k+1) now=max(now,f[y]+1);
if(!f[y]&&siz[y]>=2) flag=true;
}
flag&=(v[x].size()>=k);
f[x]=max(f[x]+flag,now);return ;
}
inline void work()
{
cin>>n>>k;
for(int i=2;i<=n;++i)
cin>>fa[i],v[fa[i]].push_back(i);
dfs(1);cout<<f[1]<<'\n';return ;
}
int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>T;
while(T--) work(),clear();
return 0;
}
D.多项式题
设 \(f_i\) 为在 \(i\) 处断开,\(1\sim i\) 所有情况的乘积的和,\(S(i,j)\) 表示 \(i\) 到 \(j\) 组成的数。
有转移:
前缀和优化,O(n)。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=2e5+10,MOD=998244353;
int n;long long f[MAXN],a[MAXN],s[MAXN];
int main()
{
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;s[0]=1;
for(int i=1;i<=n;++i)
{
char ch;cin>>ch;a[i]=ch-48;
f[i]=(f[i-1]*10%MOD+s[i-1]*a[i]%MOD)%MOD;
s[i]=(s[i-1]+f[i])%MOD;
}
cout<<f[n]<<'\n';return 0;
}