NOIP提高組模擬賽4
A. 入陣曲
丹青千秋釀,一醉解愁腸。
無悔少年枉,只願壯志狂。
矩陣字首和加暴力\(O(N^2M^2)\) 60pts有手就行
觀察資料範圍,猜測應該是求一種\(O(N^3)\)的演算法,想到之前做的題,應該是\(N^2\)列舉行,\(N\)處理一個序列的答案,然後,就沒有然後了
對於一個序列,求子段和為k的倍數,如何\(O(N)\)求解,
考慮字首和,如果一個子段和為k的倍數,那麼子段的左右兩界在模k意義下是同餘的,開陣列記錄,\(s[i]\)表示模k餘數為i的字首和有幾個,而任意兩個都對應一個合法的矩陣,所以可以愉快的切掉此題,還有一點就是\(s[0]\)初值應設為1,原因模k為0的直接就是合法矩陣。
記得開longlong,還有不要每次memset,不然你會喜提TLE35 還不如暴力
#include<cstdio> #include<cstring> using namespace std; const int maxn=405; int n,m,k; int mp[maxn][maxn]; long long sum[maxn][maxn]; int s[1000005]; int main() { freopen("rally.in","r",stdin); freopen("rally.out","w",stdout); scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) scanf("%d",&mp[i][j]); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+mp[i][j]; long long ans=0; for(int i=1;i<=n;++i) for(int x=i;x<=n;++x) { s[0]=1; for(int j=1;j<=m;++j){ long long w=(sum[x][j]-sum[i-1][j])%k; ans+=s[w];s[w]++; } for(int j=1;j<=m;++j){ long long w=(sum[x][j]-sum[i-1][j])%k; s[w]--; } } printf("%lld\n",ans); return 0; }
B. 將軍令
歷史落在贏家之手,至少我們擁有傳說
誰說敗者無法不朽,拳頭只能讓人低頭
念頭卻能讓人抬頭,抬頭去看去愛去追
你心中的夢
就我一個打DP的,思路沒啥問題,但是掛了一堆細節,調了一個多小時,結果只有20pts,然後改了億一點細節A了
畫圖可以發現,轉移時子節點不僅可能對父節點有貢獻,還可能需要父節點貢獻,設\(g[x][i]\)表示以x為根的子樹能夠駐守的距離為i的最小方案,轉移時我們需要知道\(g[v][j]\)(v是x的子節點,\(j \in [-k,k]\)),為了處理下標為負的情況,我統一加上了21,也可以像洛谷題解一樣加k。
考慮具體過程(具體程式碼下標請自行加上一個常數)
如果是葉節點 那麼\(g[x][i]=0\;(i\in [-k,-1]) g[x][i]=1\;(i\in [0,k])\)
一般情況的轉移
預處理\(ls[i]\)記錄x子樹駐守距離為i的方案之和
如果在x駐守,那麼子節點貢獻-k即可\(g[x][k]=ls[-k]+1\)
如果x有正貢獻i,但是沒有在x駐守,那麼一定有一個子節點貢獻了i+1,其他子節點貢獻-i即可\(g[x][i]=min(g[v][i+1]+ls[-i]-g[v][-i])i\in[0,k)\)
如果x有負貢獻i,那麼所有子節點貢獻了i+1才保證合法\(g[x][i]=ls[i+1]i\in[-k,-1]\)
由於某些狀態實際上不存在,所以存在\(g[x][i]>g[x][j](i< j)\)這顯然不合理,所以需要倒著取\(min\)即\(g[x][i]=min(g[x][i],g[x][i+1])\),這樣保證了i越小g也越小,且保證合法
dp到這裡就結束了,然後我們再簡單講講正解
考慮放棄DP,這題可以貪心,隨便找個根,然後找最深的點,在該點的k級祖先駐守一定最優,按點深度從大到小排序,每次取最深的點,檢查是否被控制,沒有就駐守k級父親或者根節點,暴力更新周圍點。
附dp程式碼
#include<cstdio>
#include<cstring>
using namespace std;
int min(int x,int y){return x<y?x:y;}
const int maxn=100005;
const int inf=1047483647;
struct edge{
int to,net;
}e[maxn<<1|1];
int head[maxn],tot,n,k,t;
void add(int u,int v){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
}
int g[maxn][55];
int ls[55];
void print(){
puts("");
for(int i=1;i<=n;++i){
for(int j=21-k;j<=21+k;++j)
printf("%d ",g[i][j]);
puts("");
}
}
void work(int x,int fx){
bool flag=1;
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;if(v==fx)continue;
work(v,x);flag=0;
}
if(flag){
for(int i=21-k;i<21;++i)g[x][i]=0;
for(int i=21;i<=21+k;i++)g[x][i]=1;
return;
}else{
for(int i=21-k;i<=21+k;++i)ls[i]=0;
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;if(v==fx)continue;
for(int j=21-k;j<=21+k;++j)ls[j]+=g[v][j];
}
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;if(v==fx)continue;
for(int j=0;j<k;++j)
g[x][j+21]=min(g[x][j+21],g[v][j+22]+ls[21-j]-g[v][21-j]);
}
for(int i=1;i<=k;++i)g[x][21-i]=ls[21-i+1];
g[x][k+21]=ls[21-k]+1;
for(int i=k+21-1;i>=21-k;--i)g[x][i]=min(g[x][i],g[x][i+1]);
}
//print();
}
int main()
{
freopen("general.in","r",stdin);
freopen("general.out","w",stdout);
scanf("%d%d%d",&n,&k,&t);
for(int i=1;i<n;++i){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
if(k==0){printf("%d\n",n);return 0;}
else{
memset(g,1,sizeof(g));
work(1,0);
printf("%d\n",g[1][21]);
}
return 0;
}
C. 星空
命運偷走如果只留下結果,時間偷走初衷只留下了苦衷。
你來過, 然後你走後, 只留下星空。
這題,挺神的。。。
考場由於調T2根本沒時間打T3,最後輸出2居然騙了12pts
搜尋類似分手是祝願那題,大概有72pts,可惡啊
正解需要億點巧妙轉化
首先用01表示燈亮與滅,然後用類似字首和的方式維護一個字首的差分\(cf[i]=a[i]\;xor\;a[i-1]\),注意i最大要到n+1
每次操作,相當於取反兩個數,最終目的是讓整個序列變成0
這就是
第一個轉化:
對於給定的0 1數列,每次對於按要求的某兩個數進行取反,問最少次可以使數列全部變為1.
想到分手那題,或者簡單思考一下,可以發現每次操作一定至少有1個1,如果操作兩個1,那麼他們都變成0,如果操作一個1一個0,那相當於交換他們的位置,1最多16個且位置已知,於是有了
第二個轉化
給你幾個點,每次將其中一個點移動特定的距離,如果兩個點碰到了一起,則兩個點一起消失。
問如何移動使得消去所有的點的步數最少。
所以,跑最短路,我使用了某個死去的演算法,這樣得出消去任意兩個1的最小步數,問題可以進一步轉化
第三個轉化
給你一堆物品,一次只能取出給定的一對物品,取出不同對的物品有不同的代價,問如何取出物品使得代價最小。
物品只有16個,狀壓即可
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
int min(int x,int y){return x<y?x:y;}
int max(int x,int y){return x>y?x:y;}
int flag[40005],cf[40005];
int n,k,m,op[67],x[19],cnt;
int dis[21][40005];
bool vis[40005];
queue<int>q;
void spfa(int now,int nowx){
memset(vis,0,sizeof(vis));
q.push(nowx);dis[now][nowx]=0;
while(!q.empty()){
int y=q.front();q.pop();vis[y]=0;
for(int i=1;i<=m;++i)
{
int v=y+op[i],u=y-op[i];
if(v<=n+1){
if(dis[now][v]>dis[now][y]+1){
dis[now][v]=dis[now][y]+1;
if(!vis[v]){q.push(v);vis[v]=1;}
}
}
if(u>0){
if(dis[now][u]>dis[now][y]+1){
dis[now][u]=dis[now][y]+1;
if(!vis[u]){q.push(u);vis[u]=1;}
}
}
}
}
}
int f[67737];
void dp(){
memset(f,0x3f,sizeof(f));
f[(1<<cnt)-1]=0;
for(int i=(1<<cnt)-1;i;--i){
for(int j=1;j<=cnt;++j){
if(!((1<<(j-1))&i))continue;
for(int k=j+1;k<=cnt;++k){
if(!((1<<(k-1))&i))continue;
int s=~((~i)|(1<<(j-1))|(1<<(k-1)));
f[s]=min(f[s],f[i]+dis[j][x[k]]);
}
}
}
printf("%d\n",f[0]);
}
int main()
{
freopen("starlit.in","r",stdin);
freopen("starlit.out","w",stdout);
scanf("%d%d%d",&n,&k,&m);
for(int i=1;i<=k;++i){int a;scanf("%d",&a);flag[a]=1;}
for(int i=1;i<=m;++i)scanf("%d",&op[i]);
for(int i=1;i<=n+1;++i)cf[i]=flag[i-1]^flag[i];
for(int i=1;i<=n+1;++i)if(cf[i])x[++cnt]=i;
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=cnt;++i)spfa(i,x[i]);
dp();
return 0;
}