Problem A Ciel and Robot (數學)
題意:機器人初始在(0,0),反覆執行一個移動序列,問是否能夠抵達(a,b)
題解:找出機器人第一輪的所能到達的所有位置,然後根據最後一步到達與初始位置的偏移量(dx,dy)作爲位移,看它第一輪到的所有位置(sx,sy)是否能找到一個非負整數n,滿足sx+n*dx=a且sy+n*dy=b
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dr[][2]=
{
0,1,
0,-1,
-1,0,
1,0
};
int MP[300];
int stk[105][2];
bool check(int sx,int sy,int dx,int dy,int ex,int ey)
{
int x=ex-sx,y=ey-sy;
if(dx==0)
{
if(sx!=ex)return false;
if(dy==0)return sy==ey;
y=ey-sy;
if(y/dy>=0&&y%dy==0)return true;
else return false;
}
else
{
if(x/dx>=0&&x%dx==0)
{
if(dy==0)return sy==ey;
return y%dy==0&&y/dy==x/dx;
}
else
return false;
}
}
int main()
{
//freopen("data.in","r",stdin);
MP['U']=0;
MP['D']=1;
MP['L']=2;
MP['R']=3;
int a,b;
while(scanf("%d%d",&a,&b)!=EOF)
{
char s[105];
scanf("%s",s);
int top=1,flag=0;
stk[0][0]=stk[0][1]=0;
for(int i=0; s[i]; i++)
{
stk[top][0]=stk[top-1][0]+dr[MP[s[i]]][0];
stk[top][1]=stk[top-1][1]+dr[MP[s[i]]][1];
top++;
}
int dx=stk[top-1][0],dy=stk[top-1][1];
for(int i=0; i<top&&!flag; i++)
{
if(check(stk[i][0],stk[i][1],dx,dy,a,b))
flag=1;
}
printf("%s\n",flag?"Yes":"No");
}
return 0;
}
Problem B Ciel and Duel (貪心)
題意;兩人打牌,牌有兩種,攻和守,打牌的人也是分爲攻和守,攻方手上全是攻,守方兩者皆可以有,每張牌也有自己的權值。攻方可以指定守方出某一張牌,然後他按照規則打出一張牌,規則就是:如果選出的牌是守,那麼攻方打出的牌的權值必須比守牌大,且這一回合他不能得分;如果選出的牌是攻,那麼攻方打出的牌必須不小於這張牌,得分爲他打出的牌的權值減去另一張牌權值;如果守方沒有牌了,那麼攻方剩餘的牌全部成爲他的得分。問最高得分。
題解:分兩種情況討論,一、將守方牌耗光,貪心策略爲用用最小能比守方守牌大的牌去耗掉那張守牌,守牌耗光了在用最小大於對方攻牌的牌去耗掉對方的牌;二、不將牌耗光,那麼只需要與對面攻牌打就行了,因爲對付守牌又沒得分還浪費牌,貪心策略是用最大的攻擊牌打對方最小的攻擊牌,原因如下:
記:a1,a2,b1,b2分別爲兩方攻擊牌,如果a1>a2>b1>b2,那麼無論怎麼安排,得到結果一樣;如果a1>b1>a2>b2,按照大對小,得分爲a1-b2,按照大對大,就是a1-b1+a2-b2=a1-b2+a2-b1,然而a2-b1<0,所以不如大對小好。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=105;
int atk[N],atk2[N],def2[N];
bool mark[N];
int main()
{
int n,len;
while(scanf("%d%d",&n,&len)!=EOF)
{
int lena=0,lend=0,i,j;
for(i=0;i<n;i++){
char s[5];
int v;
scanf(" %s%d",s,&v);
if(s[0]=='A')atk2[lena++]=v;
else def2[lend++]=v;
}
for(int i=0;i<len;i++)scanf("%d",&atk[i]);
sort(atk,atk+len);
sort(atk2,atk2+lena);
sort(def2,def2+lend);
memset(mark,false,sizeof(mark));
int ans1=0,ans2=0;
for(i=0,j=0;i<lend&&j<len;i++)
{
while(j<len&&(mark[j]||atk[j]<=def2[i]))j++;
if(j==len)break;
mark[j]=true;
}
if(j<len)
{
for(i=0,j=0;i<lena&&j<len;i++)
{
while(j<len&&(mark[j]||atk[j]<atk2[i]))j++;
if(j==len)break;
mark[j]=true;
ans1+=atk[j]-atk2[i];
}
if(j<len){
for(i=0;i<len;i++)
if(!mark[i])ans1+=atk[i];
}
else
ans1=0;
}
for(i=len-1,j=0;i>=0&&j<lena&&atk[i]>=atk2[j];i--,j++)
{
ans2+=atk[i]-atk2[j];
}
printf("%d\n",max(ans1,ans2));
}
return 0;
}
Problem C Ciel and Commander (樹的分治)
題意:給你一顆數,含n個結點(n<10^5),每個結點分配一個人,人的級別從‘A’~‘Z’依次遞減,求一個可行的分配方案使得任意兩個相同級別的人之間都會有至少一個比他們級別高的人在。
題解:開始用最長鏈的方式構造,結果在樣例5掛掉了,少考慮種情況。所以,這題正解還是樹的分治,選擇樹的重心(至於爲什麼選重心,可以有兩個理由,一是重心比較好,使得每個孩子的結點數能夠儘可能的平攤,二是,樹也沒有其他什麼心了,排除了所有的不可能,也就只能選它了=。=)標號爲當前可用最大級別,然後遞歸解決它的子樹,遞歸的時候讓子樹可選的最大標號爲當前根的下一級別。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=100005;
bool mark[N];
int head[N],size[N],nc;
char rank[N];
int n;
struct edge
{
int to,next;
} edge[N*3];
void add(int a,int b)
{
edge[nc].to=b;
edge[nc].next=head[a];
head[a]=nc++;
edge[nc].to=a;
edge[nc].next=head[b];
head[b]=nc++;
}
void getsize(int x,int fa)//以x爲根的子樹的結點數目
{
size[x]=1;
for(int i=head[x]; i!=-1; i=edge[i].next)
{
int t=edge[i].to;
if(mark[t]||t==fa)
continue;
getsize(t,x);
size[x]+=size[t];
}
}
int bestroot(int x)//找樹的重心
{
getsize(x,x);
int half=size[x]>>1;
while(1)
{
int id=x,mmax=0;
for(int i=head[x]; i!=-1; i=edge[i].next)
{
int t=edge[i].to;
if(!mark[t]&&mmax<size[t])
mmax=size[id=t];
}
if(mmax<=half)
break;
size[x]-=size[id];
size[id]+=size[x];
x=id;
}
return x;
}
void dfs(int root,char ra)
{
root=bestroot(root);
rank[root]=ra;
mark[root]=true;
for(int i=head[root];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(!mark[to])
dfs(to,ra+1);
}
}
int main()
{
//freopen("data.in","r",stdin);
while(scanf("%d",&n)!=EOF)
{
memset(mark,false,sizeof(mark));
memset(head,-1,sizeof(head));
nc=0;
for(int a,b,i=0; i<n-1; i++)
{
scanf("%d%d",&a,&b);
add(a,b);
}
dfs(1,'A');
for(int i=1;i<=n;i++)
printf("%c%c",rank[i],(i<n)?' ':'\n');
}
return 0;
}
Problem D Ciel and Flipboard (數學+枚舉)
題意:n*n的矩陣(n爲奇數)上每個格子都有一個整數,現在可以執行一個操作,就是選取x*x的矩形區域把裏面所有的整數取反,x=(n+1)/2,操作可以反覆執行,問最後n*n矩陣的所有元素之和的最大值是多少。
題解:一個很難想的一道題,首先,需要知道最關鍵的兩個等式。
記f[i][j]爲位置a[i][j]最後翻轉情況,取值爲-1或者1
由於x恰好等於n的一半多一點,也就是如果只考慮行,每一行的第x列肯定會被翻轉,而且第j列與第j+x列肯定不能被同時翻轉,但定會有一個被翻(這個可以畫圖理解)
行的話也是這樣,這也就可以得到兩個等式
f[i][j]*f[i][x]*f[i][j+x]=1
f[i][j]*f[x][j]*f[i+x][j]=1
這就是說確定了任意兩個都可以唯一確定第三個
通過枚舉第x列的前x行的f情況,複雜度爲2^17,然後以這幾行可以唯一確定第x列的剩下的行。
然後,對與每一種情況進行運算,方法就是單獨的枚舉第x行的前x-1列(在前x-1列,每一個元素的取值與另外的元素無關,就是說他們可以自由分配自己取多少,原因可以參考線性代數,他們實際上是線性無關的)。
當x行j列確定後,利用x行x列得到x行j+x的值,然後第j列與第j+x列的最大值可以得出來。(指定一個f[i][j],利用f[x][j]可以得到f[x+i][j],向右也可以得到f[i][j+x],f[i+x][j+x],這四個是綁定的,一個改了另三個就跟着改,可以得到兩個值,且互爲相反數,取絕對值即可)
對於f[x][j]的兩種取值可以得到j列與j+x列的最大值,然後取最大即可。
由於互相之間都不影響,所以枚舉複雜度也就是2*(x-1)*(計算列的複雜度)。
PS:代碼裏面矩陣標號用的是0~n-1,所以會有些許差異,但是思想就是這樣的。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=50;
int n,m,a[N][N],f[N][N];
int getval(int j)
{
int ans=0;
for(int i=0;i<m;i++)
ans+=abs(a[i][j]+f[i][m]*a[i][j+m+1]+f[m][j]*a[i+m+1][j]+f[i][m]*f[m][j+m+1]*a[i+m+1][j+m+1]);
return ans;
}
int solve(int mask)
{
//printf("mask=%d ",mask);
int ans=0;
for(int i=0;i<=m;i++)
{
f[i][m]=(mask&(1<<i))?-1:1;
ans+=a[i][m]*f[i][m];
}
for(int i=m+1;i<n;i++)
{
f[i][m]=f[i-m-1][m]*f[m][m];
ans+=a[i][m]*f[i][m];
}
for(int j=0;j<m;j++)
{
int ta;
f[m][j]=-1;
f[m][j+m+1]=f[m][j]*f[m][m];
ta=getval(j)+f[m][j]*a[m][j]+f[m][j+m+1]*a[m][j+m+1];
f[m][j]=1;
f[m][j+m+1]=f[m][j]*f[m][m];
ta=max(ta,getval(j)+f[m][j]*a[m][j]+f[m][j+m+1]*a[m][j+m+1]);
ans+=ta;
}
//printf("ans=%d\n",ans);
return ans;
}
int main()
{
//freopen("data.in","r",stdin);
while(scanf("%d",&n)!=EOF)
{
int ans=-10000000;
m=n>>1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&a[i][j]);
for(int mask=0,msize=1<<(m+1);mask<msize;mask++)
ans=max(ans,solve(mask));
printf("%d\n",ans);
}
return 0;
}
Problem E Ciel and Gondolas (DP優化)
題意:n個人分成m組上船(次序必須一致),每個人都有互相的不熟悉程度,在一艘船上互相不認識的程度之和就是這艘船的不熟悉度,然後所有船的不熟悉度之和就是總的,然後求總的不熟悉度的最小值。
題解:
很容易可以想到dp[i][j]=min(dp[k][j-1]+cost[k+1][i])
由不熟悉矩陣就可以看出,他們是滿足四邊形不等式優化的。
然後就是上面的dp了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=4004,M=805;
int unfam[N][N];
int cost[N][N];
int dp[N][M],s[N][M];
int row[N][N];
int n,m;
void GetCost()
{
for(int i=1; i<=n; i++)
{
row[i][0]=0;
for(int j=1; j<=n; j++)
{
row[i][j]=row[i][j-1]+unfam[i][j];
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i>=j)
cost[i][j]=0;
else
cost[i][j]=cost[i][j-1]+row[j][j]-row[j][i-1];
}
}
}
void Solve()
{
memset(dp,0x3f,sizeof(dp));
int i,j,k;
for (i=1; i<=n; i++)
{
dp[i][1]=cost[1][i];
s[i][1]=1;
}
for (j=2; j<=m; j++)
{
s[n+1][j]=n;
for (i=n; i>=j; i--)
{
for (k=s[i][j-1]; k<=s[i+1][j]; k++)
if (dp[i][j]>dp[k][j-1]+cost[k+1][i])
{
dp[i][j]=dp[k][j-1]+cost[k+1][i];
s[i][j]=k;
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
scanf("%d",&unfam[i][j]);
GetCost();
Solve();
printf("%d\n",dp[n][m]);
}
return 0;
}