【多校训练】2021HDU多校6

【前言】
这场比赛属实阴间,题十分诡异。
最后rk78,校3/9

1001. Yes Prime Minister

【题目】

给定一个数 x x x,求一个最短的区间 [ l , r ] [l,r] [l,r]使得 x ∈ [ l , r ] x\in[l,r] x[l,r] ∑ i = l r i \sum_{i=l}^ri i=lri是一个素数。

T ≤ 1 0 6 , ∣ x ∣ ≤ 1 0 7 T\leq 10^6,|x|\leq 10^7 T106,x107

【思路】

一个结论是,最后这个 ∑ \sum 至多由两个数组成(确切地说,一堆相反数加上两个连续的数)。

证明十分简单,首先一个数加上它的相反数可以变为0,这样我们就可以找到一个包含 x x x的区间,然后我们考虑正数,连续三个数一定是 3 3 3的倍数,连续四个数一定是 2 2 2的倍数,连续五个数一定是 5 5 5的倍数,于是后面也显然都不是素数了。

因此我们只需要考虑 x x x x − 1 , x + 1 x-1,x+1 x1,x+1能否组成素数,不行的话就一路往上扩增即可。

复杂度 O ( T + N ) O(T+N) O(T+N)

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=4e5+5;
int prm[maxn*100];bool vis[maxn*100];
int tot=0;
void euler(int n){
    for(int i=2;i<=n;i++){
        if(!vis[i]) prm[++tot]=i;
        for(int j=1;j<=tot&&prm[j]*i<=n;j++){
            vis[i*prm[j]]=1;
            if(i%prm[j]==0) break;
        }
    }
}
bool isprim(ll x){
    if(x<=1) return 0;
    return !vis[x];
}
bool isp(ll x){
    if(x<=1) return 0;
    for(ll i=2;i*i<=x;i++){
        if(x%i==0) return 0;
    }return 1;
}
int bf(int x,int &l,int &r){
    for(int d=1;;d++){
        for(int st=x-d+1;st<=x;st++){
            ll ed=st+d-1;
            ll sum=(ed+st)*(ed-st+1)/2ll;
            if(isp(sum)){
                l=st,r=ed;
                // cout<<st<<" "<<ed<<" ";
                return d;
            }
        }
    }
}
int calc(int x){
    // cerr<<x<<hvie;
    if(isprim(x)) return 1;
    if(isprim(x+x+1)) return 2;
    if(isprim(x+x-1)) return 2;
    int ans=x+x+1;
    for(x++;;x++,ans+=2){
        if(isprim(x)) return ans+1;
        if(isprim(x+x+1)) return ans+2;
    }
}

int main(){
    // freopen("my.out","r",stdin);
    // freopen("myout","w",stdout);
    euler(3e7+1000);
    dwn(_,yh(),1){
        int x=yh();
        if(x<0){
            int ans=-x-x+1;
            x=-x+1;
            for(;;x++,ans+=2){
                if(isprim(x)) {ans++;break;}
                if(isprim(x+x+1)) {ans+=2;break;}
            }
            cout<<ans<<hvie;
        }
        else{
            cout<<calc(x)<<hvie;
        }

    }
    return 0;
}
1002. Might and Magic

一个人的属性用 A i , D i , P i , K i , H i A_i,D_i,P_i,K_i,H_i Ai,Di,Pi,Ki,Hi表示, i = 1 i=1 i=1表示敌人, i = 0 i=0 i=0表示自己。

i i i 1 − i 1-i 1i攻击,物理攻击造成 C p max ⁡ ( 1 , A i − D 1 − i ) C_p\max(1,A_i-D_{1-i}) Cpmax(1,AiD1i)的伤害,魔法攻击造成 C m P i C_mP_i CmPi的伤害,但是魔法攻击只能使用 K i K_i Ki次。 H i H_i Hi是血量。 P 1 = K 1 = 0 P_1=K_1=0 P1=K1=0,即敌人只会物理攻击。

现在有 T T T组数据,每组数据给出 C p , C m , H 0 , A 1 , D 1 , N C_p,C_m,H_0,A_1,D_1,N Cp,Cm,H0,A1,D1,N N N N为你能分配给 A 0 , D 0 , P 0 , K 0 A_0,D_0,P_0,K_0 A0,D0,P0,K0的点数,问 H 1 H_1 H1最多是多少时你仍然能获胜(你先手)。

问题即最多能打敌人多少血。

首先我们发现我们活的轮次由防御力决定也就是我们能出手 k k k次,有 ( k − 1 ) ⋅ d < H 0 (k-1)\cdot d<H_0 (k1)d<H0,其中 d = C p ( A 1 − D 0 ) d=C_p(A_1-D_0) d=Cp(A1D0),为了方便我们显然可以把 H 0 − 1 H_0-1 H01先除以 C p C_p Cp。则 k = ⌈ H 0 − 1 d ⌉ + 1 ⇒ d = ⌊ H 0 − 1 k ⌋ + 1 k=\lceil\frac {H_0-1} {d}\rceil +1\Rightarrow d=\lfloor\frac {H_0-1} {k }\rfloor+1 k=dH01+1d=kH01+1,当然边界条件是 d = A i − D 1 − i = 1 d=A_i-D_{1-i}=1 d=AiD1i=1,即最多出手 k = H 0 k=H_0 k=H0次。

这个 k k k最多 O ( H ) O(\sqrt{H}) O(H )个取值,因此我们可以直接枚举。

接下来我们考虑分配攻击,一个猜想是我们肯定只全点物理或者全点魔法。

设分配物理攻击 x x x次,物理伤害函数 F ( x ) = x ⋅ C p max ⁡ ( 1 , x − D 1 ) F(x)=x\cdot C_p\max(1,x-D_1) F(x)=xCpmax(1,xD1),是一个下凸函数。

设分配魔法攻击 x x x次,魔法伤害函数 G ( x ) = min ⁡ ( k , x ) ⋅ C m ⋅ ( P 0 − x ) G(x)=\min(k,x)\cdot C_m\cdot (P_0-x) G(x)=min(k,x)Cm(P0x),也是一个下凸函数

总伤害函数是 P ( x ) = F ( x ) + G ( N − D 0 − x ) P(x)=F(x)+G(N-D_0-x) P(x)=F(x)+G(ND0x),关于物理加点 x x x也是一个下凸函数,于是最大值必然在一个端点处取到。

全加物理的情况就是全加 A 0 A_0 A0,全加魔法就是对 C m K 0 ( N − D 0 − K 0 ) + C p ( k − K 0 ) ( 0 ≤ K 0 ≤ min ⁡ ( k , N − D 0 ) ) C_mK_0(N-D_0-K_0)+C_p(k-K_0)(0\leq K_0\leq \min(k,N-D_0)) CmK0(ND0K0)+Cp(kK0)(0K0min(k,ND0))求最值。

复杂度 O ( T H ) O(T\sqrt H) O(TH )

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define int long long
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;

ll H0,A1,D1,n;
ll Cm,Cp;

ll magic(ll k,ll left,ll x)
{
    if(x<0 || x>min(left,k)) return 0;
    return Cm*(left-x)*x+Cp*(k-x);
}

ll calc(ll k,ll left)//D1
{
    ll ret=0;
    ret=max(ret,max(1ll,left-D1)*Cp*k);
    ret=max(ret,magic(k,left,0));
    ret=max(ret,magic(k,left,min(k,left)));
    ret=max(ret,magic(k,left,(Cm*left-Cp)/(2*Cm)));
    ret=max(ret,magic(k,left,(Cm*left-Cp-1)/(2*Cm)+1));
    return ret;
}


signed main() 
{ 
    //freopen("1002.in","r",stdin);
    //freopen("my.out","w",stdout);
    int T;scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld%lld%lld%lld%lld%lld",&Cp,&Cm,&H0,&A1,&D1,&n);
        
        ll ans=calc(1,n);
        for(int l=1,r,up=(H0-1)/Cp,k;l<=up;l=r+1)
        {
            r=up/(up/l);k=up/l+1;
           // printf("nowk:%lld %lld\n",k,r);
            int D0=A1-r;
            if(n<D0) continue;
            if(D0<0) D0=0;
            //printf("%lld %lld %lld\n",k,n-D0,calc(k,n-D0));
            ans=max(ans,calc(k,n-D0));
            //if(!D0) break;
        }
        printf("%lld\n",ans);
    }
    return 0; 
}
1003. 0 Tree

【题意】

一颗 n n n个点的树,有点权和边权。

现在可以进行不超过 4 n 4n 4n次操作,每次可以选择一条路径 u , v u,v u,v,使得 u u u v v v的点权 a u , a v a_u,a_v au,av异或上 w w w,同时 u , v u,v u,v上的每条边分别加上 ( − 1 ) k ⋅ w (-1)^k\cdot w (1)kw,其中 k k k是这是路径上第几条边。

要求最终所有点权和边权都变为 0 0 0

n ≤ 1 0 4 , w i ≤ 1 0 9 n\leq 10^4,w_i\leq 10^9 n104,wi109

【思路】

首先我们考虑点权,要让点权变为0,我们只需要从叶子开始一路异或上去就行了。

接下来我们考虑仅对一条边操作,那么连续异或一个数 w w w两次,相当于点权没有变化,而这条边加上了 w w w,而可以观察到这个边权的和,只能变大不能变小,所以我们肯定希望我们在对点操作的同时对边权影响小。

于是我们以一个叶子为根,对这棵树黑白染色,相邻两个同色点一路异或到根,这样可以使得边权和不变。操作完以后,就是根和它相连的一个点权值异或某个数,这样全部点权都为0。

接下来考虑边权,我们同样是从叶子开始,每相邻两条往根的边,把靠近叶子的边权变为0,如果可以满足要求,最后一定剩下根节点的那条边是非正权,其余边权为0,就做完了。

复杂度 O ( n ) O(n) O(n)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e4+10;

int n,tot,rt;
int head[N],du[N];
ll a[N];

struct Tway
{
    int v,nex;
    ll w;
    Tway(int _v=0,ll _w=0,int _nex=0)
    {
        v=_v;w=_w;nex=_nex;
    }
};
Tway e[N<<1];
void add(int x,int y,ll w)
{
    e[++tot]=Tway(y,w,head[x]);head[x]=tot;
    e[++tot]=Tway(x,w,head[y]);head[y]=tot;
}

struct node
{
    int x,y;
    ll w;
    node(int _x=0,int _y=0,ll _w=0)
    {
        x=_x;y=_y;w=_w;
    }
};
vector<node>ans;

void init()
{
    ans.clear();
    for(int i=1;i<=n;++i) head[i]=du[i]=a[i]=0;
    rt=tot=1;

    scanf("%d",&n);

    //printf("%d\n",n);

    for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
    for(int i=1;i<n;++i) 
    {
        int x,y;ll w;
        scanf("%d%d%lld",&x,&y,&w);
        //printf("%d %d %lld\n",x,y,w);
        add(x,y,w);++du[x];++du[y];
    }
    for(int i=1;i<=n;++i) 
    {
        if(du[i]==1)
        {
            rt=i;
            break;
        }
    }
}

void dfs1(int x,int f1,int f2)
{
    for(int i=head[x];i;i=e[i].nex)
    {
        int v=e[i].v;
        if(v!=e[f1].v) dfs1(v,i^1,f1);
    }
    if(f2)
    {
        ll w=a[x];
        a[e[f2].v]^=a[x];a[x]=0;
        e[f1].w+=w;e[f1^1].w+=w;
        e[f2].w-=w,e[f2^1].w-=w;
        ans.pb(node(x,e[f2].v,w));
    }
}

void dfs2(int x,int f1,int f2)
{
    for(int i=head[x];i;i=e[i].nex)
    {
        int v=e[i].v;
        if(v!=e[f1].v) dfs2(v,i^1,f1);
    }
    if(f2)
    {
        ll w=e[f1].w/2;
        ans.pb(node(x,e[f2].v,-w));
        ans.pb(node(x,e[f2].v,-w));
        e[f1].w=e[f1^1].w=0;
        e[f2].w+=w*2;e[f2^1].w+=w*2;
    }
}



bool solve()
{
    dfs1(rt,0,0);

    if(a[rt]^a[e[head[rt]].v])
    {
        return 0;
    }
    if(n!=1) 
    {
        ans.pb(node(rt,e[head[rt]].v,a[rt]));
    }
    e[head[rt]].w+=a[rt];e[head[rt]^1].w+=a[rt];
    
    ll sum=0;
    for(int i=2;i<=tot;i+=2) 
    {
        sum+=e[i].w;
        if((e[i].w%2+2)&1) return 0;
    }
    if(sum>0) return 0;

    dfs2(rt,0,0);
    if(n!=1) 
    {
        ans.pb(node(rt,e[head[rt]].v,-e[head[rt]].w/2));
        ans.pb(node(rt,e[head[rt]].v,-e[head[rt]].w/2));
    }
    return 1;
}

int main() 
{ 
    //freopen("1003.in","r",stdin);
    //freopen("my.out","w",stdout);
    int T;scanf("%d",&T);
    //printf("%d\n",T);
    while(T--)
    {
        init();
        if(!solve()) puts("NO");
        else
        {
            puts("YES");
            printf("%d\n",(int)ans.size());
            for(auto t:ans)
            {
                if(t.w<0) swap(t.x,t.y),t.w=-t.w;
                assert(t.w<=(ll)1e14);
                printf("%d %d %lld\n",t.x,t.y,t.w);
            }

        }
        
    }
    return 0; 
}
Decomposition

【题意】

一个 n n n个点的完全图,要求找到 k k k条简单路径,长度分别为 a i a_i ai.

n ≤ 1000 , n ≡ ( 1 mod  2 ) , k ≤ n ( n − 1 ) 2 , ∑ a i = n ( n − 1 ) 2 , a i ≤ n − 3 n\leq 1000,n\equiv(1\text{mod } 2), k\leq \frac {n(n-1)} 2,\sum a_i=\frac {n(n-1)} 2,a_i\leq n-3 n1000,n(1mod 2),k2n(n1),ai=2n(n1),ain3

【思路】

事实上题目是要求一个欧拉回路,使得任意连续 n − 2 n-2 n2个点两两不一样。

比赛的时候当然是。。。打表找规律。

考虑形如下面左图构造的一个简单环。构造方法是从中心点出发,将剩余点分为左上、右下两部分,交错地将当前点与两部分的点连边,最后回到中心点。可以证明这种构造通过不断旋转 2 π ⋅ 2 n − 1 2\pi ·\frac 2 {n-1} 2πn12弧度可以得到 n − 1 2 \frac {n-1} 2 2n1个不同的环,且这些环恰好经过完全图所有边各一次。将这些环按照旋转顺序拼接起来即可得到完全图的一条欧拉回路,可以证明这条欧拉回路任意连续n − 2 个点都两两不重复。故直接将这条欧拉回路按照题意要求顺序切分为若干路径即可。

【参考代码】

#include <bits/stdc++.h>

using namespace std;

void solve() {
	int n, t, ptr = 0; 
	scanf("%d%d", &n, &t);
	vector<int> euler(1, n-1);
	for(int i = 0; i < n/2; ++i) {
		int sgn = 1, ct = i;
		for(int d = 1; d < n; ++d) {
			euler.push_back(ct);
			ct = (ct + sgn*d + n-1) % (n-1);
			sgn *= -1;
		}
		euler.push_back(n-1);
	}
	while(t--) {
		int len; scanf("%d", &len);
		len++;
		while(len--) {
			printf("%d%c", euler[ptr++]+1, " \n"[len == 0]);
		}
		ptr--;
	}
}
int main(){
	int T; scanf("%d", &T);
	for(int i = 1; i <= T; ++i) {
		printf("Case #%d:\n", i);
		solve();
	}
    return 0;
}


1005. Median

【题目】

1 ∼ n 1\sim n 1n这些数字分成 k k k组,使得第 i i i组的中位数是 b i b_i bi(假如有 t t t个数,中位数是第 ⌊ t + 1 2 ⌋ \lfloor \frac {t+1} 2 \rfloor 2t+1小的)

n ≤ 1 0 5 n\leq 10^5 n105

【思路】

这个东西做法应该很多,比较明显的是反悔贪心,或者考虑按顺序维护每个数字在前后最多和最少能匹配多少个数。具体实现可以参考代码。。。

复杂度应该都是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

反悔贪心

/*
 * @date:2021-08-05 12:13:12
 * @source:
 */
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define up(i, l, r) for (int i = l; i <= r; ++i)
#define dn(i, l, r) for (int i = l; i >= r; --i)
#define Trav(i, x) for (auto &i : x)
#define pb push_back
template <class T, class G> bool chkMax(T &x, G y) {
    return y > x ? x = y, 1 : 0;
}
template <class T, class G> bool chkMin(T &x, G y) {
    return y < x ? x = y, 1 : 0;
}

int N, M;
set<int> s;
vi v, l;

bool solve() {
    cin >> N >> M;
    v.resize(M);
    s.clear();
    l.clear();
    up(i, 1, N) s.insert(i);
    Trav(x, v) {
        cin >> x;
        s.erase(x);
    }
    sort(ALL(v));
    int p = SZ(v) - 1;
    while (!s.empty()) {
        int x = *--s.end();
        s.erase(x);
        while (p >= 0 && v[p] > x)
            --p;
        if (p < 0) {
            s.insert(x);
            break;
        }
        if (s.empty())
            return true;
        auto a = s.lower_bound(v[p]);
        if (a == s.begin()) {
            --p;
            continue;
        }
        --a;
        l.push_back(*a);
        s.erase(*a);
    }
    if (s.empty())
        return true;
    p = 0;
    while (p < SZ(v) && v[p] < *--s.end())
        p++;
    if (p == SZ(v))
        return false;
    int cnt = 0;
    Trav(x, l) {
        if (x > v[p])
            ++cnt;
    }
    return cnt * 2 >= SZ(s);
}

string ans[2] = {"NO", "YES"};

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int Case;
    cin >> Case;
    while (Case--)
        cout << ans[solve()] << "\n";
    return 0;
}

xjb维护

#include <bits/stdc++.h>

using namespace std;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int T;
  cin >> T;
  while (T--) {
    int n, m;
    cin >> n >> m;
    assert(m >= 1 && m <= n && n <= 100000);
    vector<int> f(n + 2);
    f[n + 1] = 1;
    while (m--) {
      int v;
      cin >> v;
      assert(v >= 1 && v <= n);
      assert(f[v] == 0);
      f[v] = 1;
    }
    vector<pair<int, int>> size;
    int have = 0;
    int count = 0;
    for (int i = 1; i <= n + 1; i++) {
      if (f[i]) {
        if (have) {
          size.push_back(make_pair(have, count));
        }
        have = 0;
        count += 1;
      } else {
        have += 1;
      }
    }
    sort(size.begin(), size.end());
    if (size.empty()) {
      cout << "YES\n";
      continue;
    }
    int sum = 0;
    for (auto s: size) {
      sum += s.first;
    }
    if (size.back().first <= sum - size.back().first + size.back().second) {
      cout << "YES\n";
    } else {
      cout << "NO\n";
    }
  }
  return 0;
}

1006. The Struggle

略,不会。

1007. Power Station of Art

【题目】

有两幅 n n n个点的同构图,但是点的颜色和点权不一样(只有两种颜色)。

现在每次可以操作一条边,交换两点的点权,若两点颜色一样,则还会翻转它们的颜色。

问两幅图能不能完全一样。

n ≤ 1 0 6 n\leq 10^6 n106

【思路】

首先这个点权和颜色独立就很难办,但是我们可以修改一下它的操作:每次交换两个点的颜色和点权,然后将颜色翻转,这样显然是等价的。

现在,如果一个数字被交换奇数次,它的颜色就会翻转,否则不会。然后我们分类讨论:

  • 图是一个二分图。那么我们把这个图黑白染色,我们发现对于一个数字,如果我们知道这个数字初始的黑白染色的颜色和节点的颜色,我们把这个数字交换到任何一个位置后,对应的节点的颜色就是已知的了。而且,由于图是联通的,数字是可以任意排列到所有节点上的。那么这时条件就是将数字按照(节点颜色异或数字颜色)分成两个可重集合,题目中两个图的这两个可重集合必须是一样的。

  • 图不是一个二分图。这时候联通块里会有一个奇环。此时的条件是输入两个图里所有数字的可重集合必须相同,且颜色个数的奇偶性不能改变。这是由于我们可以把图看成一个二分图加上一些多余的边,我们可以对于任何一个奇环构造一个方案使得所有的数字不动,且只有两个位置的颜色翻转,不论那两个位置开始颜色如何。方案就是:对于奇环上的点顺序编号为1 到n,先进行n-1 次操作将1 上的数字交换到n 号节点,再进行一次操作交换1 号和n号节点上的数字,再用n-2 次操作将n 号节点上的数字交换到二号节点。

复杂度 O ( n + m ) O(n+m) O(n+m)

【参考代码】

//    苔花如米小,也学牡丹开。 
//    Zhikun Wang (nocriz)
//    $_DATE

#include <bits/stdc++.h>
using namespace std;
 
using ll = long long; using db = long double; using str = string;
using pi = pair<int,int>; using pl = pair<ll,ll>; using pd = pair<db,db>;
using vi = vector<int>; using vb = vector<bool>; using vl = vector<ll>;
using vd = vector<db>; using vs = vector<str>;
using vpi = vector<pi>; using vpl = vector<pl>; using vpd = vector<pd>;

#define tcT template<class T
#define tcTU tcT, class U
tcT> using V = vector<T>;  tcT, size_t SZ> using AR = array<T,SZ>; tcT> using PR = pair<T,T>;

#define mp make_pair 
#define f first
#define s second
#define sz(x) int((x).size())
#define bg(x) begin(x)
#define all(x) bg(x), end(x)
#define rall(x) x.rbegin(), x.rend() 
#define sor(x) sort(all(x)) 
#define rsz resize
#define ins insert 
#define ft front()
#define bk back()
#define pb push_back
#define eb emplace_back 
#define pf push_front
#define lb lower_bound
#define ub upper_bound

tcT> int lwb(V<T>& a, const T& b) { return int(lb(all(a),b)-bg(a)); }

#define FOR(i,a,b) for (int i = (a); i < (b); ++i)
#define F0R(i,a) FOR(i,0,a)
#define ROF(i,a,b) for (int i = (b)-1; i >= (a); --i)
#define R0F(i,a) ROF(i,0,a)
#define each(a,x) for (auto& a: x)

const int MOD = 998244353;
const ll INF = 1e18; // not too close to LLONG_MAX
const db PI = acos((db)-1);
const int dx[4] = {1,0,-1,0}, dy[4] = {0,1,0,-1}; // for every grid problem!!
mt19937 rng((uint32_t)chrono::steady_clock::now().time_since_epoch().count()); 
template<class T> using pqg = priority_queue<T,vector<T>,greater<T>>;

constexpr int pct(int x) { return __builtin_popcount(x); } // # of bits set
constexpr int bits(int x) { return x == 0 ? 0 : 31-__builtin_clz(x); } // floor(log2(x)) 
constexpr int p2(int x) { return 1<<x; }
constexpr int msk2(int x) { return p2(x)-1; }
ll cdiv(ll a, ll b) { return a/b+((a^b)>0&&a%b); } // divide a by b rounded up
ll fdiv(ll a, ll b) { return a/b-((a^b)<0&&a%b); } // divide a by b rounded down
tcT> bool ckmin(T& a, const T& b) { return b < a ? a = b, 1 : 0; }
tcT> bool ckmax(T& a, const T& b) { return a < b ? a = b, 1 : 0; }
tcTU> T fstTrue(T lo, T hi, U f) { hi ++; assert(lo <= hi); while (lo < hi) { T mid = lo+(hi-lo)/2; f(mid) ? hi = mid : lo = mid+1; } return lo; }
tcTU> T lstTrue(T lo, T hi, U f) { lo --; assert(lo <= hi); while (lo < hi) { T mid = lo+(hi-lo+1)/2; f(mid) ? lo = mid : hi = mid-1; } return lo; }
tcT> void remDup(vector<T>& v) { sort(all(v)); v.erase(unique(all(v)),end(v)); }
tcTU> void erase(T& t, const U& u) { auto it = t.find(u); assert(it != end(t)); t.erase(it); } 

int ans = 1;
vi vis;
vi G[1000030];
int color[2][1000030],val[2][1000030];

int can = 0,cs[2];
vi cC[2],cS[2][2];

void dfs(int num,int ccol = 2){
	if(vis[num]){
		if(vis[num]!=ccol)can = 0;
		return;
	}
	vis[num] = ccol;
	F0R(i,2){
		cC[i].pb(val[i][num]);
		cs[i]^=color[i][num];
		cS[i][(ccol-2)^color[i][num]].pb(val[i][num]);
	}
	each(ct,G[num])dfs(ct,ccol^1);
}
void solve(){
	ans = 1;
	int n,m;cin>>n>>m;
	vis.clear();vis.rsz(n+10,0);
	F0R(i,n+5)G[i].clear();
	F0R(i,m){
		int u,v;cin>>u>>v;
		G[u].pb(v);
		G[v].pb(u);
	}
	F0R(j,2){
		FOR(i,1,n+1)cin>>val[j][i];
		FOR(i,1,n+1){char ch;cin>>ch;color[j][i] = (ch == 'R');}
	}
	FOR(i,1,n+1){
		if(!vis[i]){
			F0R(j,2){
				cs[j] = 0;cC[j].clear();cS[0][j].clear();cS[1][j].clear();
			}
			can = 1;
			dfs(i);
			sor(cC[0]);sor(cC[1]);sor(cS[0][0]);sor(cS[1][0]);sor(cS[0][1]);sor(cS[1][1]);
			if(cC[0]!=cC[1])ans = 0;
			if(can && (cS[0][0]!=cS[1][0] || cS[0][1]!=cS[1][1]))ans = 0;
			if(!can && cs[0]!=cs[1])ans = 0;
		}
	}
	if(ans){
		puts("YES");
	}else{
		puts("NO");
	}
}

int main() {
	ios::sync_with_stdio(false);
	int t;cin>>t;
	while(t--){
		solve();
	}
	// END OF CODE
	return 0;
}
1008. Command and Conquer: Red Alert 2

【题目】

三维空间中有 n n n个敌人分别在 ( x i , y i , z i ) (x_i,y_i,z_i) (xi,yi,zi),你在一个位置能够打到的人是曼哈顿距离不超过 k k k的敌人,你从 ( − inf ⁡ , − inf ⁡ , − inf ⁡ ) (-\inf,-\inf,-\inf) (inf,inf,inf)出发,只能往上或往右走,问 k k k最少是多少能消灭所有敌人。

n ≤ 5 × 1 0 5 n\leq 5\times 10^5 n5×105

【思路】

考虑范围为 k k k时,攻击到的所有位置是一个边长为 2 k 2k 2k的立方体,那么问题显然可以转化为:把自己放到立方体的最下角,每次移动到所有剩下人的 ( m i n x , m i n y , m i n z ) (min_x,min_y,min_z) (minx,miny,minz)的位置,然后把 k k k增大(或不变)至能继续移动(也即把某一维最小的敌人全部消灭),这个过程是可以贪心的。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;

const int N=5e5+10,inf=2e9;
int n;
bool fg[N];

struct node
{
    int x[3],id;
    node(int a=0,int b=0,int c=0,int _id=0)
    {
        x[0]=a;x[1]=b;x[2]=c;id=_id;
    }
};
node a[N],b[N],c[N];

int op;
bool cmp(const node&a,const node&b)
{
    return a.x[op]<b.x[op];
}
void gmax(int &x,int y){x=max(x,y);}

int getk(node a,int x,int y,int z)
{
    return (max(abs(a.x[0]-x),max(abs(a.x[1]-y),abs(a.x[2]-z)))+1)/2;
}

void init()
{
    for(int i=0;i<=n;++i) fg[i]=0;
}

int main() 
{ 
    //freopen("1008.in","r",stdin);
    //freopen("my.out","w",stdout);
    int T;scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;++i) 
        {
            scanf("%d%d%d",&a[i].x[0],&a[i].x[1],&a[i].x[2]),a[i].id=i;
            b[i]=a[i];c[i]=a[i];
        }
        op=0;sort(a+1,a+n+1,cmp);
        op=1;sort(b+1,b+n+1,cmp);
        op=2;sort(c+1,c+n+1,cmp);

        int x=-inf,y=-inf,z=-inf,ix=1,iy=1,iz=1,k=0;
        for(;ix<=n;)
        {
            while(ix<=n && fg[a[ix].id]) ++ix;
            while(iy<=n && fg[b[iy].id]) ++iy;
            while(iz<=n && fg[c[iz].id]) ++iz;
            if(ix>n || iy>n || iz>n) break;

            x=a[ix].x[0];y=b[iy].x[1];z=c[iz].x[2];
            int tkx=getk(a[ix],x,y,z),tky=getk(b[iy],x,y,z),tkz=getk(c[iz],x,y,z);
            gmax(k,min(tkx,min(tky,tkz)));
            if(tkx<=k) fg[a[ix++].id]=1;
            if(tky<=k) fg[b[iy++].id]=1;
            if(tkz<=k) fg[c[iz++].id]=1;
        }
        printf("%d\n",k);
    }
    return 0; 
}
1009. Typing Contest

略,虽然想到了前半部分,但是后面不太会,数学功底不太行,就咕咕咕了,感觉不太能推广。

1010. Array

略,看题就觉得和什么方格图上的阶梯有关,不过懒得推了。

1011. Game

【题目】

博弈,每次对于一个 [ l , r ] [l,r] [l,r],可以把区间变为 [ l + 1 , r ] [l+1,r] [l+1,r] [ l , r − 1 ] [l,r-1] [l,r1],变成 [ x , x ] [x,x] [x,x]的人赢。但是现在有另外 n n n个限制,表示变成 [ a i , b i ] [a_i,b_i] [ai,bi]的人也会赢。

给出 q q q个区间 [ l i , r i ] [l_i,r_i] [li,ri],问每个比赛谁会赢。

n , q ≤ 1 0 5 , a i , b i , l i , r i ≤ 1 0 9 n,q\leq 10^5,a_i,b_i,l_i,r_i\leq 10^9 n,q105,ai,bi,li,ri109

【思路】

画个方格图,不难看出,没有特殊限制的时候就是一斜行输赢状态一样,现在新的限制会导致这个状态变化。更确切地说:如果一个点 ( i , j ) (i, j) (i,j) 任意走一步或任意走两步所到达的点都不是特殊点,那么经过简单的分类讨论, ( i + 1 , j − 1 ) (i + 1, j − 1) (i+1,j1) 的胜负状态和 ( i , j ) (i, j) (i,j) 是相同的。

特殊点只有 O ( n ) O(n) O(n)个,所以不能规约到 ( i + 1 , j − 1 ) (i + 1, j − 1) (i+1,j1)的点也只有 O ( n ) O(n) O(n) 个。把这些点全都提出来,然后跑记忆化搜索即可,普通点就直接按照 ( i + j ) (i + j) (i+j) 往下找到第一个没法规约的点,没法规约的点暴力枚举两种转移,复杂度 O ( ( n + q ) l o g ( n + q ) ) O((n + q) log(n + q)) O((n+q)log(n+q))

当然了,记忆化搜索常数有点大,于是我们还可以用扫描线做。

【参考代码】

扫描线

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=3e5+5;
int n,m;
int dt[maxn*8],cc;
int sz[maxn*32],val[maxn*32],tot=0,ls[maxn*32],rs[maxn*32];
int root[maxn*8];
struct pt{
    int x,y,z;
    bool operator<(const pt&A)const{
        return y-x<A.y-A.x;
    }
}p[maxn*8];
int ans[maxn];
map<pii,bool>spe;
#define mid ((l+r)>>1)
void ins(int &x,int l,int r,int pos,int vl){
    if(!x) {x=++tot;val[x]=-1;sz[x]=ls[x]=rs[x]=0;}
    sz[x]++;
    if(l==r) return val[x]=vl,void();
    (pos<=mid) ? ins(ls[x],l,mid,pos,vl):ins(rs[x],mid+1,r,pos,vl);
}
int ask(int x,int l,int r,int pos){
    if(!x) return -1;
    if(l==r) return val[x];
    if(pos>mid) return ask(rs[x],mid+1,r,pos);
    if(sz[ls[x]]){
        int lans=ask(ls[x],l,mid,pos);
        return ~lans?lans:ask(rs[x],mid+1,r,pos);
    }
    return ask(rs[x],mid+1,r,pos);
}

int qry(int x,int y){
    int z=lower_bound(dt+1,dt+1+cc,x+y)-dt;
    // cout<<"qry "<<x<<" "<<y<<hvie;
    if(dt[z]!=x+y) return (y-x)&1;
    int ret=ask(root[z],1,1e9,x);
    return ~ret?ret:(y-x)&1;
}
 
int main(){
    // freopen("my.in","r",stdin);
    // freopen("1011.in","r",stdin);
    // freopen("my2.out","w",stdout);
    dwn(_,yh(),1){
        spe.clear();tot=0;
        n=yh(),m=yh();
        int cp=0;
        dt[0]=0;
        rep(i,1,n){
            int x=yh(),y=yh(),z=yh();
            p[++cp]=(pt){x,y,z};
            spe[{x,y}]=z;
        }
        rep(i,1,n){
            int x=p[i].x,y=p[i].y;
            if(x>1&&!spe.count({x-1,y}))  p[++cp]=(pt){x-1,y,-1};
            if(!spe.count({x,y+1}))       p[++cp]=(pt){x,y+1,-1};
            if(x>1&&!spe.count({x-1,y+1}))p[++cp]=(pt){x-1,y+1,-1};
            if(x>2&&!spe.count({x-2,y}))  p[++cp]=(pt){x-2,y,-1};
            if(!spe.count({x,y+2}))       p[++cp]=(pt){x,y+2,-1};
            rep(k,0,2){
                dt[++dt[0]]=p[i].x+p[i].y-k;
            }
        }
        sort(dt+1,dt+1+dt[0]);
        cc=unique(dt+1,dt+1+dt[0])-dt-1;
        sort(p+1,p+1+cp);
        rep(i,1,cp)if(p[i].z==-1){
            if(p[i].x>1e9||p[i].y>1e9) break;
            // if(p[i].x>n||p[i].y>n) break;
            int x=p[i].x,y=p[i].y,
                z=lower_bound(dt+1,dt+1+cc,x+y)-dt;
            bool r,d,now;
            if(spe.count({x+1,y})) r=spe[{x+1,y}];
            else r=qry(x+1,y);
            if(spe.count({x,y-1})) d=spe[{x,y-1}];
            else d=qry(x,y-1);
            now=(!r||!d);
            // cout<<x<<" "<<y<<" : "<<r<<" "<<d<<" "<<now<<hvie;
            ins(root[z],1,1e9,x,now);
        }
        rep(__,1,m){
            int x=yh(),y=yh();
            bool r,d;
            if(spe.count({x,y})) {
                // cout<<"*";continue;
                cout<<spe[{x,y}];continue;
            }
            cout<<qry(x,y);

        }
        cout<<hvie;
        rep(i,1,dt[0]){
            root[i]=0;
        }
    }
    return 0;
}

std记忆化

#include<bits/stdc++.h>
using namespace std;
map<int,int>pos;
vector<map<int,int> >f;
int T,n,q;
const int inf=2e9;
void init()
{
	pos.clear();
	vector<map<int,int> >().swap(f);
}
int ins(int x,int y,int z)
{
	map<int,int>::iterator it;
	it=pos.find(x+y);
	if(it==pos.end())
	{
		f.push_back(map<int,int>());
		pos[x+y]=f.size()-1;	
	}
	int idx=pos[x+y];
	it=f[idx].find(x);
	int ok=0;
	if(it==f[idx].end())f[idx][x]=z,ok=1;
	else if(it->second==-1)it->second=z;
	else return it->second;
	if(ok)return -1;
	else return -2;
}
int ask(int x,int y)
{
	int res=ins(x,y,-1);
	if(res>=0)return res;
	if(y-x+1<=2)res=-2;
	map<int,int>::iterator it;
	int ans;
	if(res==-1)
	{	
		int bk=(x+y)/2,pre=inf;	
		int idx=pos[x+y];
		it=f[idx].upper_bound(x);
		if(it!=f[idx].end())pre=it->first;
		bk=min(bk,pre);
		ans=ask(bk,y-(bk-x));
	}
	else
	{
		if(x==y)ans=0;
		else ans=!(ask(x+1,y)&ask(x,y-1));
	}
	ins(x,y,ans);
	return ans;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		int cnt0=0,cnt1=0;
		scanf("%d%d",&n,&q);
		init();
		for(int i=1;i<=n;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			ins(x,y,z);
			for(int j=0;j<3;j++)for(int k=0;k<3;k++)
			{
				if(j==k&&j!=1)continue;
				if(x-j<1)continue;
				ins(x-j,y+k,-1);
			}
		}
		for(int i=1;i<=q;i++)
		{
			int x,y,tmp;
			scanf("%d%d",&x,&y);
			//tmp=ask(x,y);
			if(tmp==0)cnt0++;
			else cnt1++;
			printf("%d",ask(x,y));
		}
		printf("\n");
	//	int sum=0;
	//for(int i=0;i<f.size();i++)sum+=f[i].size();
		//printf("%d %d %d\n",cnt0,cnt1,sum);
		
	}	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值