Loading

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;
}
posted @ 2021-10-12 16:40  hyl天梦  阅读(45)  评论(0编辑  收藏  举报