【前言】
这场比赛属实阴间,题十分诡异。
最后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 T≤106,∣x∣≤107
【思路】
一个结论是,最后这个 ∑ \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 x−1,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 1−i攻击,物理攻击造成 C p max ( 1 , A i − D 1 − i ) C_p\max(1,A_i-D_{1-i}) Cpmax(1,Ai−D1−i)的伤害,魔法攻击造成 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 (k−1)⋅d<H0,其中 d = C p ( A 1 − D 0 ) d=C_p(A_1-D_0) d=Cp(A1−D0),为了方便我们显然可以把 H 0 − 1 H_0-1 H0−1先除以 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=⌈dH0−1⌉+1⇒d=⌊kH0−1⌋+1,当然边界条件是 d = A i − D 1 − i = 1 d=A_i-D_{1-i}=1 d=Ai−D1−i=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)=x⋅Cpmax(1,x−D1),是一个下凸函数。
设分配魔法攻击 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⋅(P0−x),也是一个下凸函数
总伤害函数是 P ( x ) = F ( x ) + G ( N − D 0 − x ) P(x)=F(x)+G(N-D_0-x) P(x)=F(x)+G(N−D0−x),关于物理加点 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(N−D0−K0)+Cp(k−K0)(0≤K0≤min(k,N−D0))求最值。
复杂度 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)k⋅w,其中 k k k是这是路径上第几条边。
要求最终所有点权和边权都变为 0 0 0,
n ≤ 1 0 4 , w i ≤ 1 0 9 n\leq 10^4,w_i\leq 10^9 n≤104,wi≤109
【思路】
首先我们考虑点权,要让点权变为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 n≤1000,n≡(1mod 2),k≤2n(n−1),∑ai=2n(n−1),ai≤n−3
【思路】
事实上题目是要求一个欧拉回路,使得任意连续 n − 2 n-2 n−2个点两两不一样。
比赛的时候当然是。。。打表找规律。
考虑形如下面左图构造的一个简单环。构造方法是从中心点出发,将剩余点分为左上、右下两部分,交错地将当前点与两部分的点连边,最后回到中心点。可以证明这种构造通过不断旋转 2 π ⋅ 2 n − 1 2\pi ·\frac 2 {n-1} 2π⋅n−12弧度可以得到 n − 1 2 \frac {n-1} 2 2n−1个不同的环,且这些环恰好经过完全图所有边各一次。将这些环按照旋转顺序拼接起来即可得到完全图的一条欧拉回路,可以证明这条欧拉回路任意连续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 1∼n这些数字分成 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 n≤105
【思路】
这个东西做法应该很多,比较明显的是反悔贪心,或者考虑按顺序维护每个数字在前后最多和最少能匹配多少个数。具体实现可以参考代码。。。
复杂度应该都是 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 n≤106
【思路】
首先这个点权和颜色独立就很难办,但是我们可以修改一下它的操作:每次交换两个点的颜色和点权,然后将颜色翻转,这样显然是等价的。
现在,如果一个数字被交换奇数次,它的颜色就会翻转,否则不会。然后我们分类讨论:
-
图是一个二分图。那么我们把这个图黑白染色,我们发现对于一个数字,如果我们知道这个数字初始的黑白染色的颜色和节点的颜色,我们把这个数字交换到任何一个位置后,对应的节点的颜色就是已知的了。而且,由于图是联通的,数字是可以任意排列到所有节点上的。那么这时条件就是将数字按照(节点颜色异或数字颜色)分成两个可重集合,题目中两个图的这两个可重集合必须是一样的。
-
图不是一个二分图。这时候联通块里会有一个奇环。此时的条件是输入两个图里所有数字的可重集合必须相同,且颜色个数的奇偶性不能改变。这是由于我们可以把图看成一个二分图加上一些多余的边,我们可以对于任何一个奇环构造一个方案使得所有的数字不动,且只有两个位置的颜色翻转,不论那两个位置开始颜色如何。方案就是:对于奇环上的点顺序编号为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 n≤5×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,r−1],变成 [ 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,q≤105,ai,bi,li,ri≤109
【思路】
画个方格图,不难看出,没有特殊限制的时候就是一斜行输赢状态一样,现在新的限制会导致这个状态变化。更确切地说:如果一个点 ( i , j ) (i, j) (i,j) 任意走一步或任意走两步所到达的点都不是特殊点,那么经过简单的分类讨论, ( i + 1 , j − 1 ) (i + 1, j − 1) (i+1,j−1) 的胜负状态和 ( i , j ) (i, j) (i,j) 是相同的。
特殊点只有 O ( n ) O(n) O(n)个,所以不能规约到 ( i + 1 , j − 1 ) (i + 1, j − 1) (i+1,j−1)的点也只有 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;
}