這道題比較送分
考慮格雷碼的生成方式
實際上每次如果下一位是1就和下一位是0的反轉一下
考慮再下一位,如果還是1就會再反轉一下,如果兩位是0或兩位是1就會變回原狀,否則就會剛好反轉,所以只會和前後兩位有關,推一下就可以知道格雷碼其實是
考場上沒推到,但是也可以寫,影響不大。
代碼:
#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
int n,rev,ans[100];
ull k;
int main(){
scanf("%d%llu",&n,&k);
while(n--){
if(k<(1ull<<n)){
printf("%d",rev);
rev=0;
}
else{
printf("%d",rev^1);
rev=1;
k-=(1ull<<n);
}
}
return 0;
}
Day1T2 括號樹
對於每一個點,考慮以這個點爲末尾增加了多少個合法括號串,如果是左括號就是0,如果是右括號可以用棧找到當前點匹配的括號作爲一個,再加上匹配點的父親產生的合法括號串的個數,最後每個點再算鏈上的前綴和。
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=500010;
typedef long long ll;
int n,tp,tot,f[N],head[N],to[N],nxt[N],stk[N],val[N];
ll dp[N],ans;
void add_edge(int u,int v){
nxt[++tot]=head[u];
to[tot]=v;
head[u]=tot;
return;
}
void dfs(int u){
int x=0;
if(tp>0&&val[stk[tp]]==0&&val[u]==1){
x=stk[tp--];
dp[u]+=dp[f[x]]+1;
}
else stk[++tp]=u;
for(int i=head[u];~i;i=nxt[i])
dfs(to[i]);
if(x)
stk[++tp]=x;
else tp--;
return;
}
void dfs2(int u){
ans^=dp[u]*u;
for(int i=head[u];~i;i=nxt[i]){
dp[to[i]]+=dp[u];
dfs2(to[i]);
}
return;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++){
char c;
do c=getchar();
while(c!='('&&c!=')');
if(c=='(')
val[i]=0;
else val[i]=1;
}
for(int i=2;i<=n;i++){
scanf("%d",f+i);
add_edge(f[i],i);
}
dfs(1);
dfs2(1);
printf("%lld\n",ans);
return 0;
}
Day1T3 樹上的數
考慮貪心,對於儘量小的數應該移到儘量小的點上,會發現這樣就可以產生一個邊的次序,而且邊的次序只對於一個點的所有連邊有這樣的順序。如果一個數要從s點換到t點,次序大概是這樣的:
對於s點,這條路經上的那條邊一定是最先刪除的
對於t點,這條路徑上的那條邊一定是最後刪除的
對於路徑上的點,這條路徑上的那兩條邊一定是按順序緊接着刪除的
於是我們可以用一個鏈表維護每個點所連邊之間的順序,從小到大枚舉每個數,dfs找到可以到達的最小點
具體怎麼維護每個點連邊的順序,我們可以先對所有所連邊建一個鏈表,在添加一個次序的時候合併兩個鏈表。爲了方便維護,因爲在鏈表中間的邊顯然不會再連邊,所以可以將nxt和pre直接設爲-1,如果這條邊是第一條刪除的邊,可以把pre設爲0,最後一條邊同理。
連邊和判斷能否走到下一個點具體的可以看代碼實現,有點難講。
代碼:
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
int T,n,tot,mini,fa[N],a[N],deg[N],head[N],to[N*N],nxt[N*N],pre[N][N],nt[N][N];
void add_edge(int u,int v){
nxt[++tot]=head[u];
to[tot]=v;
head[u]=tot;
return;
}
bool check(int u,int l,int r){
if(pre[u][l]==-1||nt[u][r]==-1||nt[u][r]==l||(pre[u][l]==0&&nt[u][r]==n+1&°[u]!=2))
return 0;
return 1;
}
void dfs(int u){
if(fa[u]&&check(u,fa[u],n+1))
mini=min(mini,u);
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(v==fa[u]||!check(u,fa[u],v))
continue;
fa[v]=u;
dfs(v);
}
return;
}
void merge(int u,int l,int r){
nt[u][pre[u][l]]=nt[u][r];
pre[u][nt[u][r]]=pre[u][l];
nt[u][r]=pre[u][l]=-1;
deg[u]--;
return;
}
void solve(int x){
merge(x,fa[x],n+1);
while(fa[fa[x]]){
int tmp=x;
x=fa[x];
merge(x,fa[x],tmp);
}
int tmp=x;
x=fa[x];
merge(x,0,tmp);
return;
}
int main(){
scanf("%d",&T);
while(T--){
tot=0;
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
deg[i]=2;
}
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
deg[u]++;
deg[v]++;
}
for(int i=1;i<=n;i++)
for(int j=0;j<=n+1;j++)
pre[i][j]=nt[i][j]=j;
for(int i=1;i<=n;i++){
mini=0x7f7f7f7f;
fa[a[i]]=0;
dfs(a[i]);
solve(mini);
printf("%d ",mini);
}
putchar('\n');
}
return 0;
}
Day2T1 Emiya 家今天的飯
首先顯然做的菜的數量小於等於會的烹飪方法數
考慮到直接求比較複雜,先不考慮每種食材的出現次數,求出總方案數,顯然可以一個dp或者數學推一下就出來了。
這裏給出dp的方法:
設表示前i個烹飪方法中做了j道菜的方案數,則有:
其中表示第i種烹飪方式中可以做的菜的總數。
再考慮題目的限制,顯然最多隻有一種主要食材在超過一半的菜中出現,所以我們可以直接枚舉主要食材,然後求方案數之後從總方案數減去就ok了。
怎麼在已知一個在超過一半的菜的主要食材時求出方案數呢?
考慮一個樸素的dp,表示前i種烹飪方法中做了j道菜,其中有k道菜是用已知食材做的。
那麼就可以得出:
其中now表示當前已知食材。
於是就有了的84分優秀做法。
注意到最終的狀態只有極小部分的是需要用到了,那麼是不是可以考慮一下壓縮狀態呢?
我們會發現用當前已知食材的菜超過一半,會發現這樣就會比其它所有菜的數量加起來要多。
所以我們可以把dp的後兩維壓成一維,表示當前已知食材做的菜的數量和其它食材做的菜的差,於是就有了dp方程:
代碼:
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=110,M=2010;
const int mod=998244353;
int n,m,ans,a[N][M],sum[N],dp[N][N],g[N][N*2],*f[N];
int Add(int a,int b){
return a+b>=mod?a+b-mod:a+b;
}
int Minus(int a,int b){
return a<b?a-b+mod:a-b;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",a[i]+j);
sum[i]=Add(sum[i],a[i][j]);
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
dp[i][0]=dp[i-1][0];
for(int j=1;j<=n;j++)
dp[i][j]=(dp[i-1][j]+1ll*dp[i-1][j-1]*sum[i])%mod;
}
for(int i=1;i<=n;i++)
ans=Add(ans,dp[n][i]);
for(int i=0;i<=n;i++)
f[i]=g[i]+N;
for(int now=1;now<=m;now++){
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=-n;j<=n;j++)
f[i][j]=Add(f[i-1][j],Add(1ll*f[i-1][j-1]*a[i][now]%mod,1ll*f[i-1][j+1]*Minus(sum[i],a[i][now])%mod));
for(int i=1;i<=n;i++)
ans=Minus(ans,f[n][i]);
}
printf("%d",ans);
return 0;
}
Day2T2
這道題有個的結論:最優解的最後一段會盡量小
證明的話可以用數學歸納法:
設表示前m項的答案,表示前m項的和,最後一段最小時左端點爲,最後一段存在的左端點爲
所以問題轉化爲證明不等式:
i=0就不說了
記當前所求的是前i項的答案
假設在<時原命題成立,
考慮作差法證明不等式:
原命題得證。
有了這個結論就可以直接一個單調隊列求出最後一段的左端點,然後直接算答案就好了。
代碼:
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=40000010,base=1e9;
typedef long long ll;
int n,type,hd,tl,q[N];
ll a[N],f[N];
struct data{
int a[4];
data(){
a[0]=a[1]=a[2]=a[3]=0;
}
int& operator[](int x){
return a[x];
}
friend data operator+(data x,data y){
ll ret[4];
for(int i=0;i<4;i++)
ret[i]=x[i]+y[i];
for(int i=0;i<3;i++){
ret[i+1]+=ret[i]/base;
ret[i]%=base;
}
data ans;
for(int i=0;i<4;i++)
ans[i]=ret[i];
return ans;
}
void write(){
bool ck=0;
for(int i=3;i>=0;i--)
if(a[i]&&!ck){
ck=1;
printf("%d",a[i]);
}
else if(ck)
printf("%09d",a[i]);
return;
}
};
data Sqr(ll x){
ll tmp[4]={0,0,0,0};
tmp[0]=x%base;
tmp[1]=x/base;
tmp[2]=tmp[1]*tmp[1];
tmp[1]=tmp[0]*tmp[1]*2;
tmp[0]=tmp[0]*tmp[0];
for(int i=0;i<3;i++){
tmp[i+1]+=tmp[i]/base;
tmp[i]%=base;
}
data ans;
for(int i=0;i<4;i++)
ans[i]=tmp[i];
return ans;
}
void rd0(){
for(int i=1;i<=n;i++)
scanf("%d",a+i);
return;
}
void rd1(){
int x,y,z,m,p,l,r,lstp=0;
scanf("%d%d%d%d%d%d",&x,&y,&z,a+1,a+2,&m);
for(int i=3;i<=n;i++)
a[i]=(1ll*x*a[i-1]+1ll*y*a[i-2]+z)&((1<<30)-1);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&p,&l,&r);
p=min(p,n);
for(int j=lstp+1;j<=p;j++)
a[j]=a[j]%(r-l+1)+l;
lstp=p;
}
return;
}
int main(){
scanf("%d%d",&n,&type);
if(type)
rd1();
else
rd0();
for(int i=1;i<=n;i++){
a[i]+=a[i-1];
while(hd<tl&&a[q[hd+1]]-a[f[q[hd+1]]]<=a[i]-a[q[hd+1]])
hd++;
f[i]=q[hd];
while(hd<tl&&a[q[tl]]-a[f[q[tl]]]+a[q[tl]]>a[i]-a[f[i]]+a[i])
tl--;
q[++tl]=i;
}
data ans;
for(int x=n;x;x=f[x])
ans=ans+Sqr(a[x]-a[f[x]]);
ans.write();
return 0;
}
Day2T3 樹的重心
直接在dfs的時候一邊換爲以x爲根,然後會發現一棵樹的重心會在所有重鏈的交集上,維護一個倍增跳重兒子找重心,如果有另一個重心一定是父親或者重兒子,直接判就行。
怎麼維護重兒子可以見代碼。
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300010;
typedef long long ll;
int T,n,tot,head[N],to[N*2],nxt[N*2],f[N],siz[N],son[N][20],secson[N];
ll ans;
void add_edge(int u,int v){
nxt[++tot]=head[u];
to[tot]=v;
head[u]=tot;
return;
}
void dfs1(int u){
siz[u]=1;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(v==f[u])
continue;
f[v]=u;
dfs1(v);
if(siz[v]>siz[son[u][0]]){
secson[u]=son[u][0];
son[u][0]=v;
}
else if(siz[v]>siz[secson[u]])
secson[u]=v;
siz[u]+=siz[v];
}
for(int i=1;i<=18;i++)
son[u][i]=son[son[u][i-1]][i-1];
return;
}
void dfs2(int u,int father){
int s=son[u][0],sc=secson[u],sz=siz[u];
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(v==father)
continue;
int x=v;
for(int i=17;i>=0;i--)
if(siz[v]-siz[son[x][i]]<=siz[v]/2)
x=son[x][i];
if(max(siz[v]-siz[x],siz[son[x][0]])<=siz[v]/2)
ans+=x;
if(max(siz[v]-siz[son[x][0]],siz[son[son[x][0]][0]])<=siz[v]/2)
ans+=son[x][0];
if(max(siz[v]-siz[f[x]],siz[son[f[x]][0]])<=siz[v]/2)
ans+=f[x];
siz[u]=n-siz[v];
son[u][0]=father;
f[u]=0;
if(s!=v)
if(siz[son[u][0]]<siz[s])
son[u][0]=s;
if(sc!=v)
if(siz[son[u][0]]<siz[sc])
son[u][0]=sc;
for(int i=1;i<=17;i++)
son[u][i]=son[son[u][i-1]][i-1];
x=u;
for(int i=17;i>=0;i--)
if(siz[u]-siz[son[x][i]]<=siz[u]/2)
x=son[x][i];
if(max(siz[u]-siz[x],siz[son[x][0]])<=siz[u]/2)
ans+=x;
if(max(siz[u]-siz[son[x][0]],siz[son[son[x][0]][0]])<=siz[u]/2)
ans+=son[x][0];
if(max(siz[u]-siz[f[x]],siz[son[f[x]][0]])<=siz[u]/2)
ans+=f[x];
f[u]=v;
dfs2(v,u);
}
f[u]=father;
siz[u]=sz;
secson[u]=sc;
son[u][0]=s;
for(int i=1;i<=17;i++)
son[u][i]=son[son[u][i-1]][i-1];
return;
}
int main(){
scanf("%d",&T);
while(T--){
tot=ans=0;
memset(head,-1,sizeof(head));
memset(son,0,sizeof(son));
memset(secson,0,sizeof(secson));
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs1(1);
dfs2(1,0);
printf("%lld\n",ans);
}
return 0;
}