noip模擬測試10
T1
這道題在考場上想到了二維字首和,就是自己算字首和的方式有點麻煩,導致花的時間較長,但還是成功搞了出來。
因為暴力計算的話需要不停列舉左上角和右下角的 i ,j, 時間複雜度為 n^4 ,我當時就想一種能不能減少一層或者兩層列舉內容,但是沒什麼思路。
考慮這樣一個性質:在模k意義下相同的字首和,任意兩個相減可以被 k 整除
這道題正解為 n^3 ,的複雜度,我們考慮將每一列壓縮,這樣,我們只需要通過列舉每個矩形區域的上下邊界,就可以求得答案
注意:當餘數為0的時候要多算一次答案
程式碼:
#include<bits/stdc++.h> #define re register int #define int long long #define ll long long using namespace std; const int N=410,M=1e6+10; int n,m,k,ans; int a[N][N],sum[N][N],s[N]; int vis[M]; ll read() { ll x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return x; } #undef int int main() { #define int long long n=read(); m=read(); k=read(); for(re i=1;i<=n;i++) { for(re j=1;j<=m;j++) { a[i][j]=read(); } } for(re i=1;i<=n;i++) { for(re j=1;j<=m;j++) { sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; } } for(re i=0;i<n;i++) { for(re j=i+1;j<=n;j++) { for(re p=1;p<=m;p++) { s[p]=(sum[j][p]-sum[i][p]+k)%k; ans+=vis[s[p]]++; } ans+=vis[0]; for(re p=1;p<=m;p++) vis[s[p]]=0; vis[0]=0; } } printf("%lld\n",ans); return 0; }
T2
這道題,是一道貪心題,但是我在考場上沒看出來,當時之打了個dfs。
其實不難看出,如果要修建小隊,則在距離他的最遠的祖先哪裡最優
所以我們可以按深度從大到小排序,從深度最大的兒子向上更新答案
程式碼:
#include<bits/stdc++.h> #define re register int #define next neet #define INF 9999999 using namespace std; const int N=1e5+10; int n,k,t,ans,tot; int fa[N],deep[N],num[N],o[N]; int to[N<<1],next[N<<1],head[N<<1]; struct node { int out,ff; }; int my(int a,int b) { return deep[a]>deep[b]; } node gett(int v) { node P; int u=v,out=INF,mf=v; for(re i=1;i<=k;i++) { u=fa[u]; out=min(out,o[u]+i); mf=u; if(u==0) break; } P.out=out; P.ff=mf; return P; } void change(int x) { int u=x; for(re i=1;i<=k;i++) { u=fa[u]; o[u]=min(o[u],i); if(u==0) break; } } void add(int x,int y) { to[++tot]=y; next[tot]=head[x]; head[x]=tot; } void dfs(int st,int f) { deep[st]=deep[f]+1; for(re i=head[st];i;i=next[i]) { int p=to[i]; if(p==f) continue; fa[p]=st; dfs(p,st); } } int main() { scanf("%d%d%d",&n,&k,&t); int a,b,c,d; for(re i=2;i<=n;i++) { scanf("%d%d",&a,&b); add(a,b); add(b,a); } deep[0]=-1; dfs(1,0); for(re i=1;i<=n;i++) { num[i]=i; o[i]=INF; } o[0]=INF; sort(num+1,num+n+1,my); for(re i=1;i<=n;i++) { int v=num[i]; node X; X=gett(v); o[v]=min(o[v],X.out); if(o[v]>k) { ++ans; o[X.ff]=0; change(X.ff); } } printf("%d\n",ans); return 0; }
T3
這道題,確實比較有難度
看到對於一個區間的操作,我當時想了線段樹,書裝陣列,但是沒什麼思路。
正解是利用差分!!,這道題思路非常妙,考慮一個差分陣列,那麼考慮對原 010101 串進行差分。 例如樣例,原串是 0100010,這裡設第 000 位和第 n+1n+1n+1 位都為 000,用紅色數字表示。
則差分之後是 0110011,那麼我們對原串 [l,r] ,進行翻轉,相當於把 a[l]^=1,a[r+1]^=1,
而我們的目的是將差分陣列全部變為0,考慮轉移時的情況:
1.兩端都是 0,變為 1
2.兩端都是1.變為 0
3.一個 1,一個 0;
可見,只有第二種情況對答案有貢獻,所以我們之考慮第二種情況;
我們將每個 i與i+b[j] 連一條長度為1的邊,那麼將 [L,R] 的區間翻轉的最小次數就是 L到 R 的最短路
我們將所有的 1 進行狀態壓縮,易得:
t=s^(1<<i-1)^(1<<j-1)
f[t]=min(f[t],f[s]+dis[i][j]);
最後輸出答案 f[0],即可
程式碼:
#include<bits/stdc++.h> #define re register int using namespace std; const int N=4e4+10; int n,k,m,sum; int b[N],pos[N],dis[N],q[N]; int f[(1<<16)+5]; int d[N>>2][N>>2]; bool vis[N]; void bfs() { int u,v,l,r; for(re i=1;i<=sum;i++) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[pos[i]]=0; vis[pos[i]]=1; l=r=1; q[l]=pos[i]; while(l<=r) { u=q[l]; ++l; for(re j=1;j<=m;j++) { v=u-b[j]; if(v>=1&&(!vis[v])) { dis[v]=dis[u]+1; vis[v]=1; q[++r]=v; } v=u+b[j]; if(v<=n+1&&(!vis[v])) { dis[v]=dis[u]+1; vis[v]=1; q[++r]=v; } } } for(re j=1;j<=sum;j++) d[i][j]=dis[pos[j]]; } } int main() { scanf("%d%d%d",&n,&k,&m); int a; for(re i=1;i<=k;i++) { scanf("%d",&a); vis[a]^=1; vis[a+1]^=1; } for(re i=1;i<=m;i++) scanf("%d",&b[i]); for(re i=1;i<=n+1;i++) if(vis[i]) pos[++sum]=i; bfs(); memset(f,0x3f,sizeof(f)); f[(1<<sum)-1]=0; for(re s=(1<<sum)-1;s;s--) { int p=1; int ss=s; while((ss&1)==0) { ++p; ss>>=1; } for(re i=p+1;i<=sum;i++) { int t=s^(1<<(i-1))^(1<<(p-1)); f[t]=min(f[t],f[s]+d[p][i]); } } printf("%d\n",f[0]); return 0; }