ISIJ 作業,所以來做了。
打星號的是我能力范圍內想出來的。([eJOI2018] 元素周期表很久以前見過,不清楚現在見到能不能做出來,所以不打星號)
[eJOI2017] 魔法(*)
每種字符出現次數相同,也就是每種字符的 \(s_{r,c}-s_{l-1,c}\) 相同。
注意到,若 \(\forall i,s_{l-1,i}-s_{l-1,0}=s_{r,i}-s_{r,0}\),那么 \([l,r]\) 滿足要求,反之也成立。
那么就能隨便做了。
時間復雜度看實現,我寫的垃圾 \(O(n|\Sigma|\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=1000000007;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,cnt,a[maxn],vis[128],pre[maxn][52],ans;
map<vector<int>,int> mp;
char s[maxn];
int main(){
n=read();
scanf("%s",s+1);
FOR(i,1,n){
if(!vis[s[i]]) vis[s[i]]=++cnt;
a[i]=vis[s[i]];
}
FOR(i,1,n) a[i]--;
FOR(i,1,n){
FOR(j,0,cnt-1) pre[i][j]=pre[i-1][j];
pre[i][a[i]]++;
}
FOR(i,0,n){
vector<int> v;
FOR(j,0,cnt-1) v.PB(pre[i][j]-pre[i][0]);
ans=(ans+mp[v])%mod;
mp[v]++;
}
printf("%d\n",ans);
}
[eJOI2017] 六(*)
被 AGC 虐爆后,我養成了自閉就搜的習慣。
然后在這題用上了,就離譜(
首先我們只用考慮每次加的數包含哪些質因子。設這個質因子集合為 \(S\)。那么要求要求任意時刻,\(S\) 的出現次數不超過 \(2\)。
那么設 \(f_T\),\(T\) 的第 \(S\) 位是 \(0/1/2\),表示質因子集合為 \(S\) 的數已經有了多少個。
狀態數 \(O(3^{2^k})\),沒救了。
但是這個狀態是真的不滿,直接搜搜記記。
實現優秀點就能過了。
順便聊聊我從 2s+ 變成 0.2s 的過程:
- vector 改成兩個 ll 壓位。卡進 2s。
- 三進制改成四進制(要用 ull)。效果拔群。本地已經能 0.8s,然而洛谷太老爺了。
- 修改限制方式。本來我的寫法是枚舉加哪種數,然后判斷限制,就有很多無用枚舉。現在變成加數時就更新別的數的狀態(能不能加)。這本來能過了,洛谷上 0.7s+,但后來時限改了。
- 求 \(tot_s\)(\(s\) 內的倍數的個數)用 \(O(\sqrt{n}k)\)。我也不知道當時怎么想的,改成 \(O(\sqrt{n}+2^kk)\) 就洛谷上 0.3s+ 了,目前最優解。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> PII;
const int maxn=100010,mod=1000000007;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int k,tot[64],cnt[6];
ll n,p[6];
vector<int> v[64];
map<PII,int> mp;
inline int tbit(ull x,int p){
return (x>>(p<<1))&3;
}
int dfs(ull x,ull y){
if(mp.count(MP(x,y))) return mp[MP(x,y)];
int ans=1;
FOR(i,1,(1<<k)-1) if((i<32?tbit(x,i):tbit(y,i-32))<=1){
ll tmpx=x,tmpy=y;
bool flag=true;
FOR(jj,0,(int)v[i].size()-1){
int j=v[i][jj];
if(j<32){
if(tbit(x,j)<2) x+=1ull<<(j<<1);
}
else{
if(tbit(y,j-32)<2) y+=1ull<<((j-32)<<1);
}
}
if(flag) ans=(ans+1ll*dfs(x,y)*tot[i])%mod;
x=tmpx;y=tmpy;
}
return mp[MP(x,y)]=ans;
}
int main(){
n=read();
ll tmp=n;
for(int i=2;1ll*i*i<=n;i++) if(n%i==0){
p[k]=i;
while(n%i==0) n/=i,cnt[k]++;
k++;
}
if(n>1) p[k]=n,cnt[k++]=1;
n=tmp;
FOR(i,0,(1<<k)-1){
tot[i]=1;
FOR(j,0,k-1) if((i>>j)&1) tot[i]=1ll*tot[i]*cnt[j]%mod;
}
FOR(i,1,(1<<k)-1) FOR(j,1,(1<<k)-1) if(i&j) v[i].PB(j);
printf("%d\n",(dfs(0,0)-1+mod)%mod);
}
[eJOI2017] 粒子(*)
每次操作時二分,判斷此時跑到最右的 \(x\) 粒子是否在跑到最左的 \(y\) 粒子右邊。模擬 \(k\) 次即可。
由於我相信 \(O(nk\log n)\) 不能過,我們要接着優化。
其實就是求個半平面交的事。每次重構半平面交,然后把兩邊的上半部分分段加起來,在上面判斷。
由於直線就那么幾條,所以不用每次排序。
時間復雜度 \(O(n\log n+nk)\)。
然后當我發現評測記錄里一大堆 0.9s+ 的時候心態就崩了。
第二天我過來補代碼,補着補着發現時限甚至變成了 2s,然后提交記錄前幾個 1.8s+。看起來就是暴力常數太大然后申請開大時限。
然后開始無 能 狂 怒。是真的無能,由於當時是那一天,發不了犇犇和帖子,連博客都不能發。
拿了個最優解不虧
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=50050,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct line{
int k,id1,id2;
ll b;
bool operator<(const line &l)const{
if(k!=l.k) return k<l.k;
return b>l.b;
}
}lx[maxn],ly[maxn],hx[maxn],hy[maxn],h[maxn*2];
int n,l,k,tx[maxn],vx[maxn],ty[maxn],vy[maxn],tpx,tpy,tot;
inline double interx(line l1,line l2){
return 1.0*(l1.b-l2.b)/(l2.k-l1.k);
}
line merge(line l1,line l2){
return (line){l1.k+l2.k,l1.id1,l2.id1,l1.b+l2.b};
}
int main(){
n=read();l=read();k=read();
FOR(i,1,n) tx[i]=read(),vx[i]=read(),lx[i]=(line){vx[i],i,0,-1ll*vx[i]*tx[i]};
FOR(i,1,n) ty[i]=read(),vy[i]=read(),ly[i]=(line){vy[i],i,0,-1ll*vy[i]*ty[i]};
sort(lx+1,lx+n+1);sort(ly+1,ly+n+1);
while(k--){
tpx=0;
FOR(i,1,n){
if(tpx && lx[i].k==hx[tpx].k) continue;
while(tpx>=2 && interx(lx[i],hx[tpx])<=interx(lx[i],hx[tpx-1])) tpx--;
hx[++tpx]=lx[i];
}
tpy=0;
FOR(i,1,n){
if(tpy && ly[i].k==hy[tpy].k) continue;
while(tpy>=2 && interx(ly[i],hy[tpy])<=interx(ly[i],hy[tpy-1])) tpy--;
hy[++tpy]=ly[i];
}
tot=0;
int j=1;
FOR(i,1,tpx){
double lft=i==1?-1e18:interx(hx[i],hx[i-1]),rig=i==tpx?1e18:interx(hx[i],hx[i+1]);
while(j<=tpy){
double L=j==1?-1e18:interx(hy[j],hy[j-1]),R=j==tpy?1e18:interx(hy[j],hy[j+1]);
if(L>rig) break;
if(R>=lft) h[++tot]=merge(hx[i],hy[j]);
if(R>rig) break;
j++;
}
}
int ans1=0,ans2=0;
FOR(i,1,tot){
double lft=i==1?-1e18:interx(h[i],h[i-1]),rig=i==tot?1e18:interx(h[i],h[i+1]);
double x=1.0*(l-h[i].b)/h[i].k;
if(x>=lft && x<=rig){
ans1=h[i].id1;
ans2=h[i].id2;
break;
}
}
printf("%d %d\n",ans1,ans2);
FOR(i,1,n-1) if(lx[i].id1==ans1) swap(lx[i],lx[i+1]);
FOR(i,1,n-1) if(ly[i].id1==ans2) swap(ly[i],ly[i+1]);
n--;
}
}
[eJOI2017] 駱駝(*)
居然前幾天模擬賽做過……
至少我寫的很重工業。也幸好是模擬賽做過不然根本靜不下心來寫這個(
天哪要是我在現場怕不是要被這個題耗費一生然后白給了
這個范圍提醒我們 \(5\times 5\) 分成一塊塊。
然后瞎做。我用了類似下圖的寫法。
時間復雜度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1010,mod=998244353;
const int a1[5][5]={
{1,15,25,22,14},
{8,5,19,11,6},
{17,23,2,16,24},
{20,12,7,21,13},
{9,4,18,10,3}
};
const int a2[5][5]={
{1,11,8,20,12},
{16,5,23,15,6},
{9,19,2,10,25},
{22,14,7,21,13},
{17,4,24,18,3}
};
const int a3[5][5]={
{1,6,21,18,7},
{13,16,24,10,15},
{22,19,2,5,20},
{25,9,14,17,8},
{12,4,23,11,3}
}; // go out at 21
const int a4[5][5]={
{25,8,11,22,7},
{18,3,14,17,2},
{10,21,6,9,12},
{24,16,1,23,15},
{19,4,13,20,5}
};
const int a5[5][5]={
{24,13,16,23,1},
{4,21,10,5,18},
{15,7,2,14,8},
{25,12,17,22,11},
{3,20,9,6,19}
};
const int a6[5][5]={
{9,17,1,10,18},
{14,23,20,15,24},
{4,11,8,5,2},
{21,16,25,22,19},
{13,6,3,12,7}
};
const int a7[5][5]={
{9,2,21,10,1},
{19,5,13,16,6},
{22,25,8,3,24},
{12,15,20,11,14},
{18,4,23,17,7}
};
const int a8[5][5]={
{19,6,12,20,5},
{24,16,9,23,2},
{13,21,4,14,11},
{18,7,1,17,8},
{25,15,10,22,3}
};
const int a9[5][5]={
{25,10,7,22,11},
{18,15,4,1,16},
{8,21,12,9,6},
{24,2,17,23,3},
{19,14,5,20,13}
};
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,ans[maxn][maxn],cnt;
int main(){
n=read();
if(n%10==0){
FOR(i,0,4) FOR(j,0,4) ans[i][j]=a3[i][j]<=21?a3[i][j]:n*n-(25-a3[i][j]);
cnt=1;
FOR(i,0,n/5-1) if(i%2==0){
FOR(j,1,n/5-1) if(j!=n/5-1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a1[k][l];
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a2[k][l];
cnt++;
}
}
else{
ROF(j,n/5-1,1) if(j!=1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a1[k][4-l];
cnt++;
}
else if(i!=n/5-1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a2[k][4-l];
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a5[k][l];
cnt++;
}
}
ROF(i,n/5-1,1) FOR(j,0,0){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a4[k][l];
cnt++;
}
}
else{
FOR(i,0,4) FOR(j,0,4) ans[i][j]=a3[i][j]<=21?a3[i][j]:n*n-(25-a3[i][j]);
cnt=1;
FOR(i,0,n/5-3) if(i%2==0){
FOR(j,1,n/5-1) if(j!=n/5-1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a1[k][l];
cnt++;
}
else if(i!=n/5-3){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a2[k][l];
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a5[k][4-l];
cnt++;
}
}
else{
ROF(j,n/5-1,1) if(j!=1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a1[k][4-l];
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a2[k][4-l];
cnt++;
}
}
ROF(j,n/5-1,1) if((n/5-1-j)%2==0){
FOR(i,n/5-2,n/5-1) if(i==n/5-2){
if(j==n/5-1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a6[k][l];
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a7[4-l][4-k];
}
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a7[k][l];
cnt++;
}
}
else{
ROF(i,n/5-1,n/5-2) if(i==n/5-2){
if(j==1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a8[k][l];
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a7[4-k][l];
}
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a7[4-l][k];
cnt++;
}
}
ROF(i,n/5-1,1) FOR(j,0,0) if(i!=n/5-1){
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a4[k][l];
cnt++;
}
else{
FOR(k,0,4) FOR(l,0,4) ans[i*5+k][j*5+l]=cnt*25-4+a9[k][l];
cnt++;
}
}
FOR(i,0,n-1){
FOR(j,0,n-1) printf("%d ",ans[i][j]);
puts("");
}
}
[eJOI2017] 經驗(*)
首先發現,對於每一條鏈,兩個端點的 \(w\) 一定是一個鏈上最小值一個鏈上最大值。如果不是,那么找出最小值和最大值的位置,把外面的砍掉,不會變劣。
把題目變一下:選出一個鏈划分,若每條鏈的端點是 \(u,v\),求 \(|w_u-w_v|\) 的和的最大值。
再變一下:選出一個鏈划分,若每條鏈的端點是 \(u,v\),那么你可以選定這條鏈的價值是 \(w_u-w_v\) 還是 \(w_v-w_u\),求鏈的價值的和的最大值。
那么設 \(f_{u,0/1}\) 表示 \(u\) 的子樹內,\(0\) 表示 \(u\) 所在的鏈是淺的權值減去深的權值,\(1\) 反過來,的最大答案。
時間復雜度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,el,head[maxn],to[maxn*2],nxt[maxn*2],w[maxn];
ll f[maxn][2];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs(int u,int F){
ll tot=0,mx0=-1e18,mx1=-1e18;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==F) continue;
dfs(v,u);
ll x=max(f[v][0]+w[v],f[v][1]-w[v]);
tot+=x;
mx0=max(mx0,f[v][0]-x);
mx1=max(mx1,f[v][1]-x);
}
f[u][0]=max(tot-w[u],tot+mx0);
f[u][1]=max(tot+w[u],tot+mx1);
}
int main(){
n=read();
FOR(i,1,n) w[i]=read();
FOR(i,1,n-1){
int u=read(),v=read();
add(u,v);add(v,u);
}
dfs(1,0);
printf("%lld\n",max(f[1][0]+w[1],f[1][1]-w[1]));
}
[eJOI2017] 游戲
這題居然沒想出來,大概可以退役了 /kk
顯然每次取的都是最大的數。
問題變成每次加入一個數,然后把最大值刪掉。
用一個指針記錄最大值的位置。
注意到如果加入的數比目前的最大值大,那么它會立刻被刪掉,直接處理。
否則,就暴力移指針。
指針只會往小了移,所以時間復雜度 \(O(nk)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,q,a[maxn],cnt[maxn];
int main(){
n=read();q=read();
FOR(i,1,n) a[i]=read();
while(q--){
int k=read(),cur=0;
ll ans=0;
FOR(i,1,n) cnt[i]=0;
FOR(i,1,k-1) cnt[a[i]]++,cur=max(cur,a[i]);
FOR(i,1,n){
int x=cur;
if(k+i-1<=n){
if(a[i+k-1]>cur) x=a[i+k-1];
else cnt[a[i+k-1]]++,cnt[cur]--;
}
else cnt[cur]--;
while(cur && !cnt[cur]) cur--;
if(i%2==0) ans-=x;
else ans+=x;
}
printf("%lld\n",ans);
}
}
[eJOI2018] 山(*)
兩座被選定的山肯定不能相鄰。
如果選定了保留哪些山,那么只有這些山兩旁的山會被降低。
那么隨便做了。
\(f_{i,j,0/1/2}\) 表示前 \(i\) 座山,選了 \(j\) 座留下,\(0\) 表示 \(i\) 和 \(i-1\) 都沒留下,\(1\) 表示 \(i-1\) 留下了,\(2\) 表示 \(i\) 留下了。
時間復雜度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=5050,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,a[maxn],f[maxn][maxn/2][3];
int main(){
n=read();
FOR(i,1,n) a[i]=read();
FOR(i,0,n) FOR(j,0,(n+1)/2) f[i][j][0]=f[i][j][1]=f[i][j][2]=1e9;
f[0][0][0]=0;
FOR(i,1,n) FOR(j,0,(i+1)/2){
f[i][j][0]=min(f[i-1][j][0],f[i-1][j][1]);
f[i][j][1]=f[i-1][j][2];
if(j){
f[i][j][2]=f[i-1][j-1][0]+max(0,a[i-1]-a[i]+1)+max(0,a[i+1]-a[i]+1);
if(i>=2) f[i][j][2]=min(f[i-1][j-1][1]+max(0,min(a[i-1],a[i-2]-1)-a[i]+1)+max(0,a[i+1]-a[i]+1),f[i][j][2]);
}
}
FOR(i,1,(n+1)/2) printf("%d ",min(min(f[n][i][0],f[n][i][1]),f[n][i][2]));
}
[eJOI2018] 護照
[eJOI2018] AB 串
聲明:這題我沒過。是因為洛谷計分系統的問題導致把我判成了 AC。
[eJOI2018] 元素周期表
還是很妙的。
把每行每列都抽象成點,一個點 \((x,y)\) 就將第 \(x\) 行和第 \(y\) 行連邊。
\((x1,y1),(x1,y2),(x2,y1)\) 都存在,那么第 \(x2\) 行和第 \(y2\) 行一定聯通,此時也看做 \((x2,y2)\) 存在。
所以會變成一堆聯通塊。答案就是聯通塊個數減一。
時間復雜度 \(O(n+m+q\mathrm{DSU}(n+m))\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=200020,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,q,fa[maxn*2],cnt;
int getfa(int x){
return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
int main(){
n=read();m=read();q=read();
cnt=n+m;
FOR(i,1,n+m) fa[i]=i;
while(q--){
int x=read(),y=read()+n;
x=getfa(x);y=getfa(y);
if(x!=y) fa[x]=y,cnt--;
}
printf("%d\n",cnt-1);
}
[eJOI2018] 互素樹
[eJOI2018] 循環排序
[eJOI2019] 異或橙子(*)
考慮詢問 \([l,r]\) 時,\(i\) 被異或了多少次。顯然是 \((i-l+1)(r-i+1)\)。
當 \(l,r\) 不同奇偶時,這個永遠是偶數,答案就是 \(0\)。
當 \(l,r\) 同奇偶時,當且僅當 \(i\) 和 \(l\) 同奇偶時是奇數。兩個樹狀數組維護即可。
時間復雜度 \(O((n+q)\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=200020,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,q,a[maxn],b[2][maxn];
inline void update(int *b,int p,int v){
for(int i=p;i<=n;i+=i&-i) b[i]^=v;
}
inline int query(int *b,int p){
int s=0;
for(int i=p;i;i-=i&-i) s^=b[i];
return s;
}
inline int query(int *b,int l,int r){
return query(b,r)^query(b,l-1);
}
int main(){
n=read();q=read();
FOR(i,1,n) update(b[i%2],i,a[i]=read());
while(q--){
int op=read(),x=read(),y=read();
if(op==1){
update(b[x%2],x,a[x]^y);
a[x]=y;
}
else{
if((y-x)%2==1) puts("0");
else printf("%d\n",query(b[x%2],x,y));
}
}
}
[eJOI2019] 掛架(*)
看看樣例解釋找規律,然后就是普及組模擬了。
具體一點,每次特判 \(k\le 2\),然后后面會分成長度為 \(2^1,2^2,2^3\dots\) 的段,每段會加上 \(2^{n-2},2^{n-3},2^{n-4},\dots\)。
找到 \(k\) 所在那段,加上,減去左端點,接着做。
時間復雜度看實現,反正瞎寫都能過。(給自己的垃圾 \(O(n\log k)\) 找借口)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1000100,mod=1000000007;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,pw[maxn],ans;
ll k;
int main(){
n=read();k=read();
pw[0]=1;
FOR(i,1,n) pw[i]=2*pw[i-1]%mod;
while(k){
if(k==1){ans=(ans+1)%mod;break;}
if(k==2){ans=(ans+1+pw[n-1])%mod;break;}
ll tmp=2;
int cur=n-1;
while(tmp<k) tmp*=2,cur--;
ans=(ans+pw[cur])%mod;
k-=tmp/2;
}
printf("%d\n",ans);
}
[eJOI2019] T 形覆蓋(*)
部分分啟發意義很大。
第一個包,兩兩獨立,很好做。
第二個包,對於相鄰的顯然只有一種方法,否則還是獨立的。
第三個包,對於只有一個頂點相鄰的,雖然有兩種方法,但是覆蓋到的格子完全一樣。那么我們對每個八連通塊分開考慮,八連通塊之間獨立。
只剩下 \(x_i=x_j,|y_i-y_j|=2\) 或者 \(|x_i-x_j|=2,y_i=y_j\) 了,其它的上面討論過了。
我們考慮先把其它的都確定了,然后最后剩下一堆無法確定的。把滿足上面那條式子的兩點連個邊,然后考慮每個聯通塊。
如果一個聯通塊是樹,\(c\) 個點,那么除掉 \(c\) 個中心,還剩 \(3c+1\) 個格子。所以選擇最小的一個格子不選即可。手玩發現肯定能還原放的方法。
如果一個聯通塊是基環樹,那么還剩 \(3c\) 個格子,都得選。
如果都不是,那么剩余不到 \(3c\) 個格子,一定無解。
注意當有中心在邊界上或者與已經被覆蓋的格子相鄰時,還有一點點區別,不過問題不大。
時間復雜度 \(O(nm)\)。
由於菜,寫的又臭又長。
寫的時候可以這樣寫:每覆蓋一個格子,它周圍的 T 中心都處理一下(此時對於這個 T 中心合法方案不超過一種),遞歸下去。這樣細節會少一點。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1000100,mod=998244353,d1[4][2]={{0,1},{0,-1},{-1,0},{1,0}},d2[4][2]={{1,1},{-1,-1},{-1,1},{1,-1}},d3[4][2]={{0,2},{0,-2},{2,0},{-2,0}};
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,k,ans,cnt,mn;
int *a[maxn],*tp[maxn],*vis[maxn];
bool *bl[maxn];
inline void nsol(){puts("No");exit(0);}
void work2(int,int);
void work(int x,int y,int d){
if(tp[x][y]!=-1 && tp[x][y]!=d) nsol();
if(tp[x][y]==d) return;
tp[x][y]=d;
FOR(i,0,3) if(i!=d){
int tx=x+d1[i][0],ty=y+d1[i][1];
if(tx<1 || tx>n || ty<1 || ty>m || vis[tx][ty]!=-1) nsol();
ans+=a[tx][ty];
vis[tx][ty]=i^1;
work2(tx,ty);
}
}
void work2(int x,int y){
FOR(i,0,3) if(i!=vis[x][y]){
int tx=x+d1[i][0],ty=y+d1[i][1];
if(bl[tx][ty]) work(tx,ty,i^1);
}
}
void dfs(int x,int y,int lx,int ly){
cnt+=2;
tp[x][y]=-2;
FOR(i,0,3){
int tx=x+d3[i][0],ty=y+d3[i][1];
if(tx<1 || tx>n || ty<1 || ty>m || tp[tx][ty]>=0 || !bl[tx][ty]) continue;
cnt--;
if(tp[tx][ty]==-1) dfs(tx,ty,x,y);
}
FOR(i,0,3){
int tx=x+d1[i][0],ty=y+d1[i][1];
if(tx<1 || tx>n || ty<1 || ty>m || vis[tx][ty]!=-1){
if(vis[tx][ty]!=-2) cnt-=2;
continue;
}
vis[tx][ty]=-2;
ans+=a[tx][ty];
mn=min(mn,a[tx][ty]);
}
}
int main(){
n=read();m=read();
FOR(i,0,n+1){
a[i]=new int[m+2];
bl[i]=new bool[m+2];
vis[i]=new int[m+2];
tp[i]=new int[m+2];
FOR(j,0,m+1){
a[i][j]=0;
bl[i][j]=false;
vis[i][j]=-1;
tp[i][j]=-1;
}
}
FOR(i,1,n) FOR(j,1,m) a[i][j]=read();
k=read();
while(k--){
int x=read()+1,y=read()+1;
ans+=a[x][y];
bl[x][y]=true;
vis[x][y]=-2;
FOR(i,0,3){
int tx=x+d1[i][0],ty=y+d1[i][1];
if(bl[tx][ty] && tp[x][y]==-1) work(x,y,i),work(tx,ty,i^1);
}
}
FOR(x,1,n) FOR(y,1,m) if(bl[x][y] && tp[x][y]==-1){
FOR(i,0,3){
int tx=x+d2[i][0],ty=y+d2[i][1];
if(bl[tx][ty] && tp[x][y]==-1){work(x,y,i);break;}
}
}
FOR(x,1,n) FOR(y,1,m) if(bl[x][y] && tp[x][y]==-1){
mn=1e9;cnt=0;
dfs(x,y,-1,-1);
if(cnt<0) nsol();
if(cnt==2) ans-=mn;
}
printf("%d\n",ans);
FOR(i,0,n+1){
delete[] a[i];
delete[] bl[i];
delete[] vis[i];
delete[] tp[i];
}
}
[eJOI2019] 塔(*)
先特判 \(n=1\)。
如果第 \(i\) 次操作讓 \(l_i=1,r_i=i\),那么最后會得到 \(2^{i-1}\)。
所以我們找到一個最小的 \(m\) 滿足 \(2^{m-1}\ge n\),明顯至少要 \(m\) 次操作。
而 \(m\) 次操作也是可以做到的。選一些 \(l_i\) 加一,發現會讓最后一個數減掉 \(2^{\max(1,m-i-1)}\)。
要減哪些很好弄,正確性也顯然。
時間復雜度 \(O(T\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int T,m,l[maxn],r[maxn];
ll n,tmp[maxn];
void solve(){
n=read();
if(n==1) return void(puts("0"));
m=0;
while(true){
l[++m]=1;
r[m]=m;
if(m==1) tmp[m]=1;
else tmp[m]=tmp[m-1]*2;
if(tmp[m]>=n) break;
}
n=tmp[m]-n;
reverse(tmp,tmp+m+1);
FOR(i,1,m-1) if(n>=tmp[i]) n-=tmp[i],l[i]++;
printf("%d\n",m);
FOR(i,1,m) printf("%d %d\n",l[i],r[i]);
}
int main(){
T=read();
while(T--) solve();
}
[eJOI2019] 矩形染色(*)
先考慮另一個問題:每次可以染一行或一列。
容易發現,要么所有行都選,要么所有列都選。因為如果有一行不選,那么為了把那行都染上色,只能所有列都選。有一列不選同理。
到這題我們也用類似的想法。
首先黑白染色,那么黑白部分獨立。這樣接下來看着會稍微舒服一些。下面以包括左上角的那部分為例。
我們稍微轉個 45 度,然后會變成個形如這樣的東西。
我們從左往右枚舉哪些列沒有被選。先判掉所有列都選了的情況。
\(f_i\) 表示考慮了前 \(i\) 列,第 \(i\) 列沒有被選的最小代價。
轉移枚舉上一個沒選的列 \(j\),由於這個圖很特殊,只需要知道 \(j\),就能知道少選第 \(i\) 列就要多選哪些行。只不過情況有點多。
發現可以線段樹優化,時間復雜度 \(O((n+m)\log(n+m))\)。
代碼很惡心。雖然可能純粹是因為我菜而寫得很丑。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=400040,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct segtree{
int n;
ll mn[maxn*4];
inline void pushup(int o){
mn[o]=min(mn[o<<1],mn[o<<1|1]);
}
void build(int o,int l,int r){
if(l==r) return void(mn[o]=1e18);
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(o);
}
inline void build(int n){
build(1,1,n);
this->n=n;
}
void update(int o,int l,int r,int p,ll v){
if(l==r) return void(mn[o]=v);
int mid=(l+r)>>1;
if(mid>=p) update(lson,p,v);
if(mid<p) update(rson,p,v);
pushup(o);
}
inline void update(int p,ll v){
update(1,1,n,p,v);
}
ll query(int o,int l,int r,int ql,int qr){
if(l>=ql && r<=qr) return mn[o];
int mid=(l+r)>>1;
if(mid<ql) return query(rson,ql,qr);
if(mid>=qr) return query(lson,ql,qr);
return min(query(lson,ql,qr),query(rson,ql,qr));
}
inline ll query(int l,int r){
l=max(l,1);r=min(r,n);
if(l>r) return 1e18;
return query(1,1,n,l,r);
}
}t1,t2,t3;
int n,m,a[maxn],b[maxn];
ll f[maxn],g[maxn],apre[maxn],bpre[maxn];
ll work(int beg){
t1.build(n+m-1);t2.build(n+m-1);t3.build(n+m-1);
ll ans=0;
for(int i=beg;i<=n+m-1;i+=2){
ans+=b[i];
if(i<=m){
int l=m-i+1,r=m+i-1;
ll c=apre[r]-apre[max(0,l-2)];
f[i]=c+(i==1?0:bpre[i-2]);
f[i]=min(f[i],t1.query(1,i)+c+(i==1?0:bpre[i-2]));
t1.update(i,f[i]-c-bpre[i]);
t2.update(i,f[i]-apre[r]-bpre[i]);
t3.update(i,f[i]-bpre[i]);
}
else if(i<=n){
int l=i-m+1,r=m+i-1;
ll c=apre[r]-apre[max(0,l-2)];
f[i]=c+bpre[i-2];
f[i]=min(f[i],t3.query(1,l-m)+c+bpre[i-2]);
f[i]=min(f[i],t1.query(l-m+1,2*m-i)+c+bpre[i-2]);
f[i]=min(f[i],t2.query(2*m-i+1,i)+apre[r]+bpre[i-2]);
t2.update(i,f[i]-apre[r]-bpre[i]);
t3.update(i,f[i]-bpre[i]);
}
else{
int l=i-m+1,r=m+2*n-i-1;
ll c=apre[r]-apre[max(0,l-2)];
f[i]=c+bpre[i-2];
f[i]=min(f[i],t3.query(1,l-m)+c+bpre[i-2]);
f[i]=min(f[i],t1.query(l-m+1,m-l+1)+c+bpre[i-2]);
f[i]=min(f[i],t2.query(m-l+2,2*n-i)+apre[r]+bpre[i-2]);
f[i]=min(f[i],t3.query(2*n-i+1,i)+bpre[i-2]);
t3.update(i,f[i]-bpre[i]);
}
}
int upr=n+m-1;
if(upr%2!=beg%2) upr--;
for(int i=beg;i<=n+m-1;i+=2) ans=min(ans,f[i]+bpre[upr]-bpre[i]);
return ans;
}
int main(){
n=read();m=read();
FOR(i,1,n+m-1) a[i]=read();
FOR(i,1,n+m-1) b[i]=read();
if(n<m){
swap(n,m);
reverse(a+1,a+n+m);
}
FOR(i,1,n+m-1){
if(i==1) apre[i]=a[i],bpre[i]=b[i];
else apre[i]=apre[i-2]+a[i],bpre[i]=bpre[i-2]+b[i];
}
printf("%lld\n",work(1)+work(2));
}
[eJOI2019] 箭頭國探險(*)
這個中二的題目名是我自己弄的
我們把對 \((i,j)\) 的轉向變成走到 \((i,j)\) 上再轉。
\(f_{i,j}\) 表示走到 \((i,j)\) 的最小代價。
用最短路轉移。
時間復雜度 \(O(nm\log nm)\),寫四個隊列也可以做到 \(O(nm)\) 帶個不小的常數。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=505,MAXN=maxn*maxn,MAXM=MAXN*4,mod=998244353,d[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct node{
int u,d;
bool operator<(const node &nd)const{return d>nd.d;}
};
int n,m,tp[128],el,head[MAXN],to[MAXM],nxt[MAXM],w[MAXM],dis[MAXN];
char mp[maxn][maxn];
priority_queue<node> pq;
inline int id(int x,int y){
return 1ll*(x-1)*m+y;
}
inline void add(int u,int v,int w_){
to[++el]=v;nxt[el]=head[u];head[u]=el;w[el]=w_;
}
int main(){
tp['E']=0;tp['S']=1;tp['W']=2;tp['N']=3;
n=read();m=read();
FOR(i,1,n) scanf("%s",mp[i]+1);
FOR(i,1,n) FOR(j,1,m) if(mp[i][j]!='X') FOR(k,0,3){
int x=i+d[k][0],y=j+d[k][1];
if(x<1 || x>n || y<1 || y>m) continue;
add(id(i,j),id(x,y),(k-tp[mp[i][j]]+4)%4);
}
FOR(i,1,n*m) dis[i]=1e9;
dis[1]=0;
pq.push((node){1,0});
while(!pq.empty()){
int u=pq.top().u,d=pq.top().d;pq.pop();
if(d>dis[u]) continue;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]>d+w[i]) pq.push((node){v,dis[v]=d+w[i]});
}
}
printf("%d\n",dis[id(n,m)]==1e9?-1:dis[id(n,m)]);
}