10.12 做题笔记
T1
这个题是自己想出来的。
不难发现,\(a+b\) 在变化过程中是不会变化的。知道这个性质我们就可以拿到 \(60\) 分。这是因为如果 \(a\) 变成 \(c\),那么 \(b\) 一定变成了 \(d\)。所以我们只用关注 \(a\) 即可。设 \(a+b=sum\),不难发现 \(a\) 有两种变化:
- \(a:=2a\)
- \(a:=a-(sum-a)=2a-sum\)
所以,如果 \(a\) 能够变成 \(c\) 的话,我们就有 \(2^ka-sum\times t=c\),其中 \(t\) 是一个二进制位数不超过 \(k\) 的一个整数。
不难验证上面这个东西的正确性。所以,我们可以枚举 \(k\),然后计算这个时候的 \(t\),判断 \(t\) 的合法性即可。
当 \(2^k\ge p\) 的时候,不难发现这个时候 \(t\) 是一定有解的,所以总复杂度为 \(O(q\log p)\)
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N number
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int p,q,a,b,c,d;
inline int Digit(int x){
if(!x) return 0;
int cnt=0;while(x){cnt++;x>>=1;}return cnt;
}
inline ll ksm(ll a,ll b,ll mod){
ll res=1;
while(b){if(b&1) res=res*a%mod;a=a*a%mod;b>>=1;}return res;
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(p);read(q);
int w=Digit(p);
while(q--){
// printf("here\n");
read(a);read(b);read(c);read(d);
ll sum=(a+b)%p;
ll inv=ksm(sum,p-2,p);
bool op=0;
if((a+b)%p!=(c+d)%p){puts("-1");continue;}
if(a==c&&b==d){puts("0");continue;}
for(int k=0;k<=w*2;k++){
ll now=((a*((1ll<<k)%p)%p-c)*inv%p+p)%p;
int cnt1=Digit(now);
int cnt2=Digit(now+p);
if(cnt1<=k||cnt2<=k){
printf("%lld\n",k);op=1;
break;
}
}
if(!op){puts("-1");continue;}
}
}
T2
这个题其实充分利用了一些性质,这个时候应该就要大胆乱搞。
首先 \(O(3^n)\) 可以过掉前两个 subtask,这是因为跑不满。但实际上我们可以直接枚举子集,然后看是否有和相等的两个子集就可以了,复杂度可以优化到 \(2^n\)。
关注到一个性质,\(k\) 除了前两个 subtask,都是大于等于 \(25\) 的,前两个 subtask 我们直接数据点分治过掉,然后我们考虑后面的点。
我们这样做,我们先从大到小进行排序,然后除了最后的 \(25\) 个数,前面的数贪心的分,分成两个部分,设差的绝对值为 \(w\),容易发现,\(w\le W\)。然后我们考虑后面的 \(25\) 个数,我们仍然是枚举子集,然后看是否存在两个子集使得他们的差为 \(w\)。子集个数为 \(2^{25}\),而这 \(25\) 个数的子集最大值为 \(25W\),其中 \(2^{25}\ge 26W\),再加上数据时随机分布的,所以大概一定会出现两个集合差为 \(x\),这个根据鸽巢原理可以得到。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M 5000100
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,K,a[N],b[N],cha;
vector<int> ans1,ans2;
bool op;
bool op2;
struct node{
int val,id;
inline node(){}
inline node(int val,int id) : val(val),id(id) {}
inline bool operator < (const node &b) const{
if(val!=b.val) return val<b.val;
else return id<b.id;
}
}A[N];
inline void dfs(int k){
if(op) return;
if(k==n+1){
ll sum1=0,sum2=0,cnt=0,cnt1=0,cnt2=0;
for(int i=1;i<=n;i++){
if(b[i]==0) cnt++;
else if(b[i]==1) {sum1+=a[i];cnt1++;}
else if(b[i]==2) {sum2+=a[i];cnt2++;}
}
if(cnt>K) return;
if(sum1!=sum2) return;
op=1;
// printf("sum1=%d sum2=%d\n",sum1,sum2);
printf("%lld ",cnt1);for(int i=1;i<=n;i++) if(b[i]==1) printf("%d ",i);puts("");
printf("%lld ",cnt2);for(int i=1;i<=n;i++) if(b[i]==2) printf("%d ",i);puts("");
return;
}
b[k]=0;dfs(k+1);
b[k]=1;dfs(k+1);
b[k]=2;dfs(k+1);
}
ll vis[M];
inline void dfs2(int k,int now){
if(op2) return;
if(k==26){
int tot=0;
for(int i=n-24;i<=n;i++){
if(b[i-n+25]) tot+=A[i].val;
}
vis[tot]=now;
if(tot-cha>=0&&vis[tot-cha]) op2=1;
if(tot+cha<=5000000&&vis[tot+cha]) op2=1;
return;
}
now<<=1;
b[k]=0;dfs2(k+1,now);
b[k]=1;dfs2(k+1,now^1);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(K);
for(int i=1;i<=n;i++) read(a[i]);
if(n<=25){
dfs(1);
if(!op) puts("-1");return 0;
}
for(int i=1;i<=n;i++) A[i]=node(a[i],i);
sort(A+1,A+n+1);reverse(A+1,A+n+1);
int now=0;
for(int i=1;i<=n-25;i++){
if(now<=0){now+=A[i].val;ans1.push_back(A[i].id);}
else{now-=A[i].val;ans2.push_back(A[i].id);}
}
cha=abs(now);
//now=ans1-ans2
dfs2(1,0);
int ans11,ans12;
for(int i=0;i<=5000000-cha;i++){
if(vis[i]&&vis[i+cha]){
op=1;int now=vis[i]&vis[i+cha];
ans11=vis[i]^now;
ans12=vis[i+cha]^now;
// printf("i=%d vis[i]=%d vis[i+cha]=%d ans11=%d ans12=%d\n",i,vis[i],vis[i+cha],ans11,ans12);
break;
}
}
if(!op){
puts("-1");return 0;
}
if(now<0) swap(ans11,ans12);
for(int i=n-24;i<=n;i++){
int digit=i-n+25;
digit=(25-digit);
if((ans11>>digit)&1) ans1.push_back(A[i].id);
if((ans12>>digit)&1) ans2.push_back(A[i].id);
}
int sum1=0,sum2=0;
printf("%d ",ans1.size());
for(int i=0;i<ans1.size();i++){
printf("%d ",ans1[i]);
// sum1+=a[ans1[i]];
}
printf("\n");
printf("%d ",ans2.size());
for(int i=0;i<ans2.size();i++){
printf("%d ",ans2[i]);
// sum2+=a[ans2[i]];
}
// printf("cha=%d sum1=%d sum2=%d\n",cha,sum1,sum2);
// puts("");
return 0;
}
T3
这个题该想到的性质都想到了,没有捅破最后一层窗户纸。
以下我们把如果知道某个点的工资称作选某个点。
有这样的性质:
- 如果某个点被选了,那么其父亲一定只有一个儿子。
- 一个点被方案二选到,则其子树大小一定大于等于 \(K+1\)。
- 一个点被方案一选到,则其父亲存在一个子树大小为 \(2\) 的儿子,且其余儿子要么子树大小为 \(1\),要么子树大小大于等于 \(K+1\),并且其父亲至少有 \(K\) 个儿子。
不难发现限制条件的关键是父亲,所以当我们遍历到其父亲时在考虑是否选儿子。
首先算出每棵子树大小。
然后考虑 dp,\(f_k\)。状态就不说了。
首先有 \(f_k:=\sum\limits_{s\in son_k}f_s\)
然后我们考虑选 \(k\) 的儿子。关注方案二,我们直接选最大的 \(f_{to}\) 保证 \(size_{to}\ge K+1\) 即可,然后就是 \(f_k:=\max(f_k,f_{to}+1)\)。
关注方案一,我们寻找是否存在一个儿子,其 \(f\) 值为 \(0\)。考虑到一个节点的 \(f\) 值为 \(0\) 我们可以通过删除节点来令其大小为 \(2\)。注意到方案一和方案二是不能共存的,因为方案二需要删除其他儿子。
除了这个限制,我们只需要让儿子数量够就可以了,考虑一个 \(f\) 值不为 \(0\) 的儿子子树大小必定大于等于 \(K+1\),其余 \(f\) 值为 \(0\) 的儿子总可以通过删边来使其满足条件。
注意方案一需要累加其儿子的 \(f\) 值。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 800010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){return a<b?b:a;}
struct edge{
int to,next;
inline void Init(int to_,int ne_){
to=to_;next=ne_;
}
}li[N];
int head[N],tail;
inline void Add(int from,int to){
li[++tail].Init(to,head[from]);
head[from]=tail;
}
int f[N],n,K,t,Size[N];
inline void Init(){
read(n);read(K);
for(int i=2;i<=n;i++){
int x;read(x);Add(x,i);
}
}
inline void Clear(){
tail=0;
for(int i=1;i<=n;i++) head[i]=0;
}
inline void dfs(int k,int fa){
Size[k]=1;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa) continue;
dfs(to,k);Size[k]+=Size[to];
}
}
inline void Dp(int k,int fa){
f[k]=0;
int maxx=-INF;
int Soncnt=0;
bool op1=0;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa) continue;
Dp(to,k);
if(Size[to]>=K+1) maxx=Max(maxx,f[to]);
f[k]+=f[to];
Soncnt++;
if(!f[to]&&Size[to]>=2) op1=1;
}
if(op1&&Soncnt>=K) f[k]++;
f[k]=Max(f[k],maxx+1);
}
inline void Solve(){
// printf("here\n");
dfs(1,0);Dp(1,0);
printf("%d\n",f[1]);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(t);
while(t--){
// printf("here\n");
Init();Solve();
Clear();
}
}
T4
水题放最后,不愧是毒瘤出题人。
写出 dp 方程来发现是 1d1d 模型,然后可以用前缀和优化,直接做即可。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=998244353;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
inline int ksm(int a,int b,int mod){
int res=1;
while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;
}
int n,sum[N],TenPow[N],InvTenPow[N],f[N],g[N],h[N],H[N];
char s[N];
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
scanf("%s",s+1);
int TenInv=ksm(10,mod-2,mod);
InvTenPow[0]=1;TenPow[0]=1;
for(int i=1;i<=n;i++) InvTenPow[i]=1ll*InvTenPow[i-1]*TenInv%mod;
for(int i=1;i<=n;i++) TenPow[i]=1ll*TenPow[i-1]*10%mod;
for(int i=1;i<=n;i++) sum[i]=(1ll*sum[i-1]*10%mod+s[i]-'0')%mod;
for(int i=0;i<=n;i++) h[i]=1ll*sum[i]*InvTenPow[i]%mod;
f[0]=1;g[0]=1;H[0]=h[0]*f[0];
for(int i=1;i<=n;i++){
f[i]=((1ll*sum[i]*g[i-1]%mod-1ll*TenPow[i]*H[i-1]%mod)%mod+mod)%mod;
g[i]=(g[i-1]+f[i])%mod;H[i]=(H[i-1]+1ll*h[i]*f[i]%mod)%mod;
}
printf("%d\n",f[n]);
return 0;
}