題目提交地址:http://acm.zju.edu.cn/onlinejudge/showProblems.do?contestId=1&pageNumber=31
A題
題意:給你n和m,問你怎麼安排這m個東東可以分數最高和最低。
思想:肯定m個連續起來分數最高,最低的話考慮插空,存在(n-m+1),n是(n-m+1)多少倍就是最少得分多少。
B題
題意:給你一棵樹,其中有m個點是紅色,並且保證1是根節點而且1是紅色的,然後有q次詢問,每次有k個點,問你將樹上某點變成紅色,讓當前集合的最大的花費盡量小。問你q次詢問花費裏面的最小的是多少,輸出。對於花費就相等於當前點到離他最近的紅色點的路徑權值和。
思想:預處理ST表,將k個點的花費排序,從大到小,對於集合的點,肯定是消除最大的那個話費讓它變小,這樣才能變小。所以枚舉其他點,考慮他們的LCA,不斷的比較染色(前幾個點的LCA和當前點的)的LCA造成的差值。不斷的求LCA,對於前邊的點肯定是高度不斷往上,肯定值會變大,不會比原來小,所以只需要考慮最大的花費的點改變之後的花費和當前點染色變成LCA的花費,取一個最大的即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
struct node{
int v;
ll value;
int next;
}no[maxn<<1];
int n,m,cnt;
int head[maxn];
int flag[maxn];//dfs標記是否訪問過
int vis[maxn];//標記紅色點
int color[maxn];//標記顏色
int deep[maxn];//深度
int pre[maxn][30];//ST表
int p[maxn];//存放k個點
ll dist[maxn];
void init(int _n)
{
dist[1]=0;
cnt=0;
memset(head,-1,sizeof(head[0])*(_n+1));
memset(vis,0,sizeof(vis[0])*(_n+1));
memset(flag,0,sizeof(flag[0])*(_n+1));
}
void add(int u,int v,ll value)
{
no[cnt].v=v;
no[cnt].value=value;
no[cnt].next=head[u];
head[u]=cnt++;
no[cnt].v=u;
no[cnt].value=value;
no[cnt].next=head[v];
head[v]=cnt++;
}
void dfs(int u,int co,int fa,int de)
{
flag[u]=1;
color[u]=co;
pre[u][0]=fa;
deep[u]=de;
for(int i=head[u];i!=-1;i=no[i].next)
{
int v=no[i].v;
if(flag[v])
continue;
dist[v]=dist[u]+no[i].value;
if(vis[v])
dfs(v,v,u,de+1);
else
dfs(v,co,u,de+1);
}
}
void init_lca()
{
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
pre[i][j]=pre[pre[i][j-1]][j-1];
}
int lca(int x, int y)
{
if(deep[x] < deep[y])
swap(x, y);
int temp = 0;
while((1<<temp) <= deep[x])
temp++;
temp--;
for(int i = temp;i >= 0;i--)
{
if(deep[x] - (1<<i) >= deep[y])
x = pre[x][i];
}
if(x == y)
return x;
for(int i = temp;i >= 0;i--)
{
if( pre[x][i] != pre[y][i])
{
x = pre[x][i];
y = pre[y][i];
}
}
return pre[x][0];
}
int cmp(int a,int b)//花費從大到小
{
return (dist[a]-dist[color[a]]) > (dist[b]-dist[color[b]]);
}
int main()
{
int t,q;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&m,&q);
init(n);
for(int i=0;i<m;i++)
{
int temp;
scanf("%d",&temp);
vis[temp]=1;
}
for(int i=1;i<n;i++)
{
int a,b;
ll value;
scanf("%d%d%lld",&a,&b,&value);
add(a,b,value);
}
dfs(1,1,0,1);//u color father deep
init_lca();
while(q--)
{
int k;
scanf("%d",&k);
for(int i=0;i<k;i++)
scanf("%d",&p[i]);
sort(p,p+k,cmp);
int now = p[0];
int i=0;
ll ans = (1ll<<62)-1;
while(i<k)
{
int LCA=lca(now,p[i]);
while(i<k && LCA == lca(now,p[i]) && color[now]==color[p[i]])//公共祖先是一個 而且 是同一個紅色染色
i++;
ll res = dist[p[0]]-dist[color[p[0]]] - dist[LCA] + dist[color[LCA]];
if(i<k)
res=max(res,dist[p[i]]-dist[color[p[i]]]);
if(res<ans)
ans=res;
else
break;
now = LCA;
}
printf("%lld\n",ans);
}
}
return 0;
}
C題
題意:給你n次操作,每次操作都是五種操作中的一種,問你是否是一個死循環程序。
思想:對於一個操作如果進行2次肯定就是死程序了,所以只需要判斷操作的編號即可。
H題
題意:給你一個字符串,1表示可以走(花1s),0表示等1s再走(也就是花2s),然後讓你求一個
和。
思想:從後往前考慮下,最後一個點單獨考慮,如果最後一個點是0那就是花2s,如果是1花1s。
然後對於其他的從後往前考慮下:如果當前是0的話,那麼對於後邊的是沒有影響的,因爲你等2s之後後邊的還是原來的樣子,所以只需要結果加上2*(len-i) 代表的是後邊需要加多少個t(p,q),每一個都需要增加2s。對於當前是1的話,考慮下1s之後的問題。如果往後一個是1的話,那麼你直接過去之後你還需要2s才能過去,然而你當時計算的時候只是1s,所以這樣的話結果就需要增加(len-i) (對於當前1來說都需要加1s) + (len-i-1)(對於後邊的從1變成0又多了1s)。如果往後一個是0的話,那麼你等1s過去0就變成了1s了,這樣時間就少了,所以結果需要(len-i)-(len-i-1) (與上同理)
#include<bits/stdc++.h>
using namespace std;
char str[100005];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",str);
int len=strlen(str);
long long ans,pre;
if(str[len-1]=='1')//最後一個單獨考慮下
{
pre=1;
ans=1;
}
else
{
pre=2;
ans=2;
}
for(int i=len-2;i>=0;i--)
{
if(str[i]=='1')
{
if(str[i+1]=='0')
pre+=(len-i)-(len-i-1);
else
pre+=(len-i)+(len-i-1);
ans+=pre;
}
else//當前是0 所有的的結果都需要加上2
{
pre+=(len-i)*2;
ans+=pre;
}
}
printf("%lld\n",ans);
}
return 0;
}
J題
題意:a,b,c,d,t,v; 代表的是a的倍數按燈b下,c的倍數按燈d下,每次按完之後燈會亮v秒,總共是ts
思想:求最後那個valu,考慮下對於a和c來說肯定有一個值是交接點,題意就可以分解成,有多少個是a,c獨立的區間(循環節)*這個區間的valu+非獨立區間的結果即可。所以就可以變成模擬2個區間的情況求解。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
ll a,b,c,d,v,t;
scanf("%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&v,&t);
ll ans1 = -1;
ll ans2 = 0;
ll lca = a/__gcd(a,c)*c;
ll m = t%lca;
ll C= t / lca;
ll tdata = 0;
ll i = 1ll;
ll j = 1ll;
if(C==0)
lca = m;
int flag=1;
while(1)
{
ll t;
if(a*i<c*j)
{
t=a*i;
if(t>m)
break;
i++;
}
else
{
t=c*j;
if(t>m)
break;
j++;
}
if(t-tdata>v)
ans1--;
tdata=t;
}
ans1 = ans1 +i*b+j*d;
if(C)
{
tdata=0;
i=j=1;
while(1)
{
ll t;
if(a*i<c*j)
{
t=a*i;
if(t>lca)
break;
i++;
}
else
{
t=c*j;
if(t>lca)
break;
j++;
}
if(t-tdata>v)
ans2--;
tdata=t;
}
ans2=ans2+(i-1)*b+(j-1)*d;
ans2*=C;
}
printf("%lld\n",ans1+ans2);
}
return 0;
}
K題
題意:就是給你n個數,然後求一個集合保證集合中任何2個數XOR的值小於這兩個數的最小值。
思想:轉換成求一個最高位是1而且高度一樣的數最多的那個即可。對於每個數只要最高位是第幾位,然後每次對於判斷的位置取max即可。